diff --git a/.classpath b/.classpath deleted file mode 100644 index 980887b3a..000000000 --- a/.classpath +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..93c4b27b4 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +github: processing +custom: https://processingfoundation.org/ diff --git a/.gitignore b/.gitignore index 4a58f294e..073f9ee0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,15 @@ -bin -android-core.zip -release - -.AppleDouble -._* -*~ -.DS_Store \ No newline at end of file +.gradle +.idea + +**/examples/**/AndroidManifest.xml + +**/*.iml +**/.DS_Store +**/build +**/bin +**/dist + +**/local.properties +.gradle + +.java-version diff --git a/.project b/.project deleted file mode 100644 index 9b2d77470..000000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - android-mode - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/README.md b/README.md index 087d7b230..743c8422c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,6 @@ Processing for Android ====================== -Repository for all Android-related development for Processing. +This is the main repository for Processing for Android. It includes the core library inside the core folder, and the mode itself in the root. See the [wiki](https://github.com/processing/processing-android/wiki) for build instructions. -We've moved this out from the main project so that it won't hamper progress on the desktop side, and in the hope that others will be more inclined to help and contribute on this isolated portion of the project. -This is primarily the source for the Android Mode, with the 'core' library found inside a subfolder of the same name. - -Please, please help us keep this code up to date and debugged by making fixes and submitting your pull requests here. The entire Processing project is developed by a tiny number of people working during their free time, and we could really use the help. - -Ben Fry -21 April 2013 diff --git a/apps/armarkers/build.gradle b/apps/armarkers/build.gradle new file mode 100644 index 000000000..bd89203e2 --- /dev/null +++ b/apps/armarkers/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 33 + defaultConfig { + applicationId "processing.tests.armarkers" + minSdkVersion 23 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + namespace 'armarkers' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + implementation project(':libs:processing-core') + implementation project(':libs:processing-ar') + implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'com.google.ar:core:1.35.0' +} \ No newline at end of file diff --git a/apps/armarkers/gradle.properties b/apps/armarkers/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/apps/armarkers/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/apps/armarkers/src/main/AndroidManifest.xml b/apps/armarkers/src/main/AndroidManifest.xml new file mode 100644 index 000000000..fcf1ffe6b --- /dev/null +++ b/apps/armarkers/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + diff --git a/apps/armarkers/src/main/assets/earth.jpg b/apps/armarkers/src/main/assets/earth.jpg new file mode 100644 index 000000000..73ebe8431 Binary files /dev/null and b/apps/armarkers/src/main/assets/earth.jpg differ diff --git a/apps/armarkers/src/main/java/armarkers/MainActivity.java b/apps/armarkers/src/main/java/armarkers/MainActivity.java new file mode 100644 index 000000000..beaf9990f --- /dev/null +++ b/apps/armarkers/src/main/java/armarkers/MainActivity.java @@ -0,0 +1,111 @@ +package armarkers; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.content.Intent; +import android.provider.Settings; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import processing.android.PFragment; +import processing.android.CompatUtils; +import processing.core.PApplet; + +public class MainActivity extends AppCompatActivity { + private static final int CAMERA_PERMISSION_CODE = 0; + private static boolean CAMERA_PERMISSION_REQUESTED = false; + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + private static final String CAMERA_PERMISSION_MESSAGE = "Camera permission is needed to use AR"; + + private PApplet sketch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FrameLayout frame = new FrameLayout(this); + frame.setId(CompatUtils.getUniqueViewId()); + setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + sketch = new Sketch(); + PFragment fragment = new PFragment(sketch); + fragment.setView(frame, this); + } + + @Override + protected void onResume() { + super.onResume(); + if (!hasCameraPermission()) requestCameraPermission(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (!hasCameraPermission()) { + Toast.makeText(this, CAMERA_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); + if (!shouldShowRequestPermissionRationale()) { + launchPermissionSettings(); + } + finish(); + } + + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + CAMERA_PERMISSION_REQUESTED = false; + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } + + private boolean hasCameraPermission() { + int res = ContextCompat.checkSelfPermission(this, CAMERA_PERMISSION); + return res == PackageManager.PERMISSION_GRANTED; + } + + private void requestCameraPermission() { + if (!CAMERA_PERMISSION_REQUESTED) { + CAMERA_PERMISSION_REQUESTED = true; + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE); + } + } + + private boolean shouldShowRequestPermissionRationale() { + return ActivityCompat.shouldShowRequestPermissionRationale(this, CAMERA_PERMISSION); + } + + private void launchPermissionSettings() { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", this.getPackageName(), null)); + this.startActivity(intent); + } +} diff --git a/apps/armarkers/src/main/java/armarkers/Sketch.java b/apps/armarkers/src/main/java/armarkers/Sketch.java new file mode 100644 index 000000000..c6e51856c --- /dev/null +++ b/apps/armarkers/src/main/java/armarkers/Sketch.java @@ -0,0 +1,51 @@ +package armarkers; + +import java.util.ArrayList; + +import processing.ar.*; +import processing.core.PApplet; +import processing.core.PImage; +import processing.core.PShape; + +public class Sketch extends PApplet { + ARTracker tracker; + ARAnchor anchor; + PShape earth; + + public void settings() { + fullScreen(AR); + } + + public void setup() { + fullScreen(AR); + + tracker = new ARTracker(this); + + PImage earthImg = loadImage("earth.jpg"); + tracker.start(); + tracker.addImage("earth", earthImg); + + earth = createShape(SPHERE, 0.15f); + } + + public void draw() { + lights(); + + if (mousePressed) { + // Create new anchor at the current touch point + if (anchor != null) anchor.dispose(); + ARTrackable hit = tracker.get(mouseX, mouseY); + if (hit != null && hit.isImage() && hit.getName().equals("earth")) { + anchor = new ARAnchor(hit); + } + else anchor = null; + } + + if (anchor != null) { + anchor.attach(); + shape(earth); + anchor.detach(); + } + } + +} diff --git a/apps/armarkers/src/main/res/layout/main.xml b/apps/armarkers/src/main/res/layout/main.xml new file mode 100644 index 000000000..4b602d5f6 --- /dev/null +++ b/apps/armarkers/src/main/res/layout/main.xml @@ -0,0 +1,7 @@ + diff --git a/apps/armarkers/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/armarkers/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/armarkers/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/armarkers/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/armarkers/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/armarkers/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/armarkers/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/armarkers/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/armarkers/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/armarkers/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/armarkers/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/armarkers/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/armarkers/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/armarkers/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aee44e138 Binary files /dev/null and b/apps/armarkers/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/armarkers/src/main/res/values/strings.xml b/apps/armarkers/src/main/res/values/strings.xml new file mode 100644 index 000000000..f57f444bd --- /dev/null +++ b/apps/armarkers/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + AR Test + diff --git a/apps/armarkers/src/main/res/values/styles.xml b/apps/armarkers/src/main/res/values/styles.xml new file mode 100644 index 000000000..375954d4a --- /dev/null +++ b/apps/armarkers/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + diff --git a/apps/arscene/build.gradle b/apps/arscene/build.gradle new file mode 100644 index 000000000..ea151d4d9 --- /dev/null +++ b/apps/arscene/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'com.android.application' +} + +android { + defaultConfig { + applicationId "processing.tests.arscene" + minSdkVersion 23 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + namespace 'arscene' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + implementation project(':libs:processing-core') + implementation project(':libs:processing-ar') + implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'com.google.ar:core:1.35.0' +} diff --git a/apps/arscene/gradle.properties b/apps/arscene/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/apps/arscene/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/apps/arscene/src/main/AndroidManifest.xml b/apps/arscene/src/main/AndroidManifest.xml new file mode 100644 index 000000000..fcf1ffe6b --- /dev/null +++ b/apps/arscene/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + diff --git a/apps/arscene/src/main/java/arscene/MainActivity.java b/apps/arscene/src/main/java/arscene/MainActivity.java new file mode 100644 index 000000000..8e1837290 --- /dev/null +++ b/apps/arscene/src/main/java/arscene/MainActivity.java @@ -0,0 +1,111 @@ +package arscene; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.content.Intent; +import android.provider.Settings; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import processing.android.PFragment; +import processing.android.CompatUtils; +import processing.core.PApplet; + +public class MainActivity extends AppCompatActivity { + private static final int CAMERA_PERMISSION_CODE = 0; + private static boolean CAMERA_PERMISSION_REQUESTED = false; + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + private static final String CAMERA_PERMISSION_MESSAGE = "Camera permission is needed to use AR"; + + private PApplet sketch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FrameLayout frame = new FrameLayout(this); + frame.setId(CompatUtils.getUniqueViewId()); + setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + sketch = new Sketch(); + PFragment fragment = new PFragment(sketch); + fragment.setView(frame, this); + } + + @Override + protected void onResume() { + super.onResume(); + if (!hasCameraPermission()) requestCameraPermission(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (!hasCameraPermission()) { + Toast.makeText(this, CAMERA_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); + if (!shouldShowRequestPermissionRationale()) { + launchPermissionSettings(); + } + finish(); + } + + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + CAMERA_PERMISSION_REQUESTED = false; + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } + + private boolean hasCameraPermission() { + int res = ContextCompat.checkSelfPermission(this, CAMERA_PERMISSION); + return res == PackageManager.PERMISSION_GRANTED; + } + + private void requestCameraPermission() { + if (!CAMERA_PERMISSION_REQUESTED) { + CAMERA_PERMISSION_REQUESTED = true; + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE); + } + } + + private boolean shouldShowRequestPermissionRationale() { + return ActivityCompat.shouldShowRequestPermissionRationale(this, CAMERA_PERMISSION); + } + + private void launchPermissionSettings() { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", this.getPackageName(), null)); + this.startActivity(intent); + } +} diff --git a/apps/arscene/src/main/java/arscene/Sketch.java b/apps/arscene/src/main/java/arscene/Sketch.java new file mode 100644 index 000000000..cf01c9b38 --- /dev/null +++ b/apps/arscene/src/main/java/arscene/Sketch.java @@ -0,0 +1,103 @@ +package arscene; + +import java.util.ArrayList; + +import processing.ar.*; +import processing.core.PApplet; + +public class Sketch extends PApplet { + ARTracker tracker; + ARAnchor touchAnchor; + ArrayList trackAnchors; + float angle; + + public void settings() { + fullScreen(AR); + } + + public void setup() { + tracker = new ARTracker(this); + tracker.start(); + trackAnchors = new ArrayList(); + } + + public void draw() { + // The AR Core session, frame and camera can be accessed through Processing's surface object + // to obtain the full information about the AR scene: +// ARSurface surface = (ARSurface) getSurface(); +// surface.camera.getPose(); +// surface.frame.getLightEstimate(); + + lights(); + + if (mousePressed) { + // Create new anchor at the current touch point + if (touchAnchor != null) touchAnchor.dispose(); + ARTrackable hit = tracker.get(mouseX, mouseY); + if (hit != null) touchAnchor = new ARAnchor(hit); + else touchAnchor = null; + } + + // Draw objects attached to each anchor + for (ARAnchor anchor : trackAnchors) { + if (anchor.isTracking()) drawBox(anchor, 255, 255, 255); + + // It is very important to dispose anchors once they are no longer tracked. + if (anchor.isStopped()) anchor.dispose(); + } + if (touchAnchor != null) drawBox(touchAnchor, 255, 0, 0); + + // Conveniency function in the tracker object to remove disposed anchors from a list + tracker.clearAnchors(trackAnchors); + + // Draw trackable planes + for (int i = 0; i < tracker.count(); i++) { + ARTrackable trackable = tracker.get(i); + if (!trackable.isTracking()) continue; + + pushMatrix(); + trackable.transform(); + if (mousePressed && trackable.isSelected(mouseX, mouseY)) { + fill(255, 0, 0, 100); + } else { + fill(255, 100); + } + beginShape(); + float[] points = trackable.getPolygon(); + for (int n = 0; n < points.length / 2; n++) { + float x = points[2 * n]; + float z = points[2 * n + 1]; + vertex(x, 0, z); + } + endShape(); + popMatrix(); + } + + angle += 0.1; + } + + public void drawBox(ARAnchor anchor, int r, int g, int b) { + anchor.attach(); + fill(r, g, b); + rotateY(angle); + box(0.15f); + anchor.detach(); + } + + public void trackableEvent(ARTrackable t) { + if (trackAnchors.size() < 10) { + float x0 = 0, y0 = 0; + if (t.isWallPlane()) { + // The new trackable is a wall, so adding the anchor 0.3 meters to its side + x0 = 0.3f; + } else if (t.isFloorPlane()) { + // The new trackable is a floor plane, so adding the anchor 0.3 meters above it + y0 = 0.3f; + } else { + // The new trackable is a floor plane, so adding the anchor 0.3 meters below it + y0 = -0.3f; + } + trackAnchors.add(new ARAnchor(t, x0, y0, 0)); + } + } +} diff --git a/apps/arscene/src/main/res/layout/main.xml b/apps/arscene/src/main/res/layout/main.xml new file mode 100644 index 000000000..4b602d5f6 --- /dev/null +++ b/apps/arscene/src/main/res/layout/main.xml @@ -0,0 +1,7 @@ + diff --git a/apps/arscene/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/arscene/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/arscene/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/arscene/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/arscene/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/arscene/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/arscene/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/arscene/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/arscene/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/arscene/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/arscene/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/arscene/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/arscene/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/arscene/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aee44e138 Binary files /dev/null and b/apps/arscene/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/arscene/src/main/res/values/strings.xml b/apps/arscene/src/main/res/values/strings.xml new file mode 100644 index 000000000..f57f444bd --- /dev/null +++ b/apps/arscene/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + AR Test + diff --git a/apps/arscene/src/main/res/values/styles.xml b/apps/arscene/src/main/res/values/styles.xml new file mode 100644 index 000000000..375954d4a --- /dev/null +++ b/apps/arscene/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + diff --git a/apps/fast2d/build.gradle b/apps/fast2d/build.gradle new file mode 100644 index 000000000..9a69129be --- /dev/null +++ b/apps/fast2d/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'com.android.application' +} + +android { + defaultConfig { + applicationId "processing.tests.fast2d" + minSdkVersion 17 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + namespace 'fast2d' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + implementation project(':libs:processing-core') + implementation 'androidx.appcompat:appcompat:1.6.0' +} diff --git a/apps/fast2d/gradle.properties b/apps/fast2d/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/apps/fast2d/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/apps/fast2d/src/main/AndroidManifest.xml b/apps/fast2d/src/main/AndroidManifest.xml new file mode 100644 index 000000000..5204a615a --- /dev/null +++ b/apps/fast2d/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/Topics/Shaders/BlurFilter/data/blur.glsl b/apps/fast2d/src/main/assets/blur.glsl similarity index 100% rename from examples/Topics/Shaders/BlurFilter/data/blur.glsl rename to apps/fast2d/src/main/assets/blur.glsl diff --git a/examples/Basics/Shape/DisableStyle/data/bot1.svg b/apps/fast2d/src/main/assets/bot1.svg similarity index 100% rename from examples/Basics/Shape/DisableStyle/data/bot1.svg rename to apps/fast2d/src/main/assets/bot1.svg diff --git a/apps/fast2d/src/main/assets/edges.glsl b/apps/fast2d/src/main/assets/edges.glsl new file mode 100644 index 000000000..42d4383ba --- /dev/null +++ b/apps/fast2d/src/main/assets/edges.glsl @@ -0,0 +1,36 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +varying vec4 vertColor; +varying vec2 vertTexCoord; +varying float vertTexFactor; + +uniform sampler2D texture; +uniform vec2 texScale; + +void main(void) { + vec2 tc0 = vertTexCoord.st + vec2(-texScale.s, -texScale.t); + vec2 tc1 = vertTexCoord.st + vec2( 0.0, -texScale.t); + vec2 tc2 = vertTexCoord.st + vec2(+texScale.s, -texScale.t); + vec2 tc3 = vertTexCoord.st + vec2(-texScale.s, 0.0); + vec2 tc4 = vertTexCoord.st + vec2( 0.0, 0.0); + vec2 tc5 = vertTexCoord.st + vec2(+texScale.s, 0.0); + vec2 tc6 = vertTexCoord.st + vec2(-texScale.s, +texScale.t); + vec2 tc7 = vertTexCoord.st + vec2( 0.0, +texScale.t); + vec2 tc8 = vertTexCoord.st + vec2(+texScale.s, +texScale.t); + + vec4 col0 = texture2D(texture, tc0); + vec4 col1 = texture2D(texture, tc1); + vec4 col2 = texture2D(texture, tc2); + vec4 col3 = texture2D(texture, tc3); + vec4 col4 = texture2D(texture, tc4); + vec4 col5 = texture2D(texture, tc5); + vec4 col6 = texture2D(texture, tc6); + vec4 col7 = texture2D(texture, tc7); + vec4 col8 = texture2D(texture, tc8); + + vec4 sum = 8.0 * col4 - (col0 + col1 + col2 + col3 + col5 + col6 + col7 + col8); + gl_FragColor = vec4(sum.rgb, 1.0) * vertColor; +} diff --git a/apps/fast2d/src/main/assets/frag.glsl b/apps/fast2d/src/main/assets/frag.glsl new file mode 100644 index 000000000..94fd39b36 --- /dev/null +++ b/apps/fast2d/src/main/assets/frag.glsl @@ -0,0 +1,10 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +varying vec4 vertColor; + +void main() { + gl_FragColor = vertColor; +} diff --git a/apps/fast2d/src/main/assets/img.png b/apps/fast2d/src/main/assets/img.png new file mode 100644 index 000000000..82fa26300 Binary files /dev/null and b/apps/fast2d/src/main/assets/img.png differ diff --git a/examples/Topics/Shaders/EdgeDetect/data/leaves.jpg b/apps/fast2d/src/main/assets/leaves.jpg similarity index 100% rename from examples/Topics/Shaders/EdgeDetect/data/leaves.jpg rename to apps/fast2d/src/main/assets/leaves.jpg diff --git a/apps/fast2d/src/main/assets/vert.glsl b/apps/fast2d/src/main/assets/vert.glsl new file mode 100644 index 000000000..6c0069068 --- /dev/null +++ b/apps/fast2d/src/main/assets/vert.glsl @@ -0,0 +1,16 @@ +uniform mat4 transform; + +attribute vec3 position; +attribute vec4 color; + +varying vec4 vertColor; + +void main() { + gl_Position = transform * vec4(position, 1); + + //we avoid affecting the Z component by the transform + //because it would mess up our depth testing + gl_Position.z = position.z; + + vertColor = color.zyxw; +} diff --git a/apps/fast2d/src/main/java/fast2d/MainActivity.java b/apps/fast2d/src/main/java/fast2d/MainActivity.java new file mode 100644 index 000000000..c164255fa --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/MainActivity.java @@ -0,0 +1,104 @@ +package fast2d; + +import android.os.Bundle; +import android.content.Intent; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.appcompat.app.AppCompatActivity; + +import processing.android.PFragment; +import processing.android.CompatUtils; +import processing.core.PApplet; + +public class MainActivity extends AppCompatActivity { +// private int TEST = 1; // Basic self-intersecting polygon +// private int TEST = 2; // Mouse controlled polygon +// private int TEST = 3; // Textured poly +// private int TEST = 4; // Text rendering + private int TEST = 5; // Shapes benchmark +// private int TEST = 6; // Duplicated vertex +// private int TEST = 7; // User-defined contours +// private int TEST = 8; // Primitive types +// private int TEST = 9; // Arc test +// private int TEST = 10; // Arc test +// private int TEST = 11; // Load and display SVG +// private int TEST = 12; // Filter test +// private int TEST = 13; // Custom shader test (texture) +// private int TEST = 14; // Custom shader test (no texture) + + private PApplet sketch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FrameLayout frame = new FrameLayout(this); + frame.setId(CompatUtils.getUniqueViewId()); + setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + if (TEST == 1) { + sketch = new SketchBasicPoly(); + } else if (TEST == 2) { + sketch = new SketchMousePoly(); + } else if (TEST == 3) { + sketch = new SketchTexturedPoly(); + } else if (TEST == 4) { + sketch = new SketchDisplayText(); + } else if (TEST == 5) { + sketch = new SketchShapeBenchmark(); + } else if (TEST == 6) { + sketch = new SketchDuplicatedVert(); + } else if (TEST == 7) { + sketch = new SketchUserDefinedContours(); + } else if (TEST == 8) { + sketch = new SketchPrimitiveTypes(); + } else if (TEST == 9) { + sketch = new SketchArcTest(); + } else if (TEST == 10) { + sketch = new SketchCurveTest(); + } else if (TEST == 11) { + sketch = new SketchLoadDisplaySVG(); + } else if (TEST == 12) { + sketch = new SketchFilterTest(); + } else if (TEST == 13) { + sketch = new SketchCustomShader(); + } else if (TEST == 14) { + sketch = new SketchShaderNoTex(); + } + + PFragment fragment = new PFragment(sketch); + fragment.setView(frame, this); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (sketch != null) { + sketch.onRequestPermissionsResult( + requestCode, permissions, grantResults); + } + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } +} diff --git a/apps/fast2d/src/main/java/fast2d/Sketch.java b/apps/fast2d/src/main/java/fast2d/Sketch.java new file mode 100644 index 000000000..4bd118d34 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/Sketch.java @@ -0,0 +1,560 @@ +package fast2d; + +import android.opengl.GLES20; + +import processing.core.PApplet; +import processing.core.PShape; +import processing.opengl.PShader; +import processing.core.PImage; +import processing.core.PFont; +import processing.core.PVector; +import java.util.ArrayList; +import processing.opengl.PGraphics2DX; + +public class Sketch extends PApplet { + boolean keyboard = false; + boolean wireframe = false; + + int join = MITER, cap = SQUARE, mode = OPEN; + + PImage img; + PFont font; + + float sc = 1; + float weight = 1; + + boolean runDemo[] = new boolean[10]; + + //useful for debugging + private boolean printDemo = false; + + //data for demo 2 + int[] c = new int[4096]; + ArrayList points = new ArrayList(); + + + public void settings() { + fullScreen(P2DX); +// fullScreen(P2D); + } + + public void setup() { +// orientation(LANDSCAPE); + + //pardon the silly image + img = loadImage("leaves.jpg"); + font = createFont("SansSerif", displayDensity * 72); + + //setup for demo 2 + for (int i = 0; i < c.length; ++i) { + c[i] = color(random(255), random(255), random(255)); + } + } + + public void draw() { + background(255); + + fill(255, 0, 63, 127); + stroke(255, 0, 255, 127); + strokeWeight(12 * displayDensity); + strokeJoin(ROUND); + noStroke(); + + if (keyPressed && key == 'z') { + sc /= 1.01; + } else if (keyPressed && key == 'x') { + sc *= 1.01; + } else if (keyPressed && key == 'c') { + weight /= 1.01; + } else if (keyPressed && key == 'v') { + weight *= 1.01; + } + + scale(sc); + +// println(); +// println("FRAME #" + frameCount); +// println(); + + if (frameCount % 10 == 0) println((int) frameRate + " fps"); + + strokeCap(cap); + strokeJoin(join); + + if (runDemo[5]) demo5(); + if (runDemo[2]) demo2(); + fill(255, 0, 255, 127); + if (runDemo[1]) demo1(); + if (runDemo[3]) demo3(); + if (runDemo[4]) demo4(); + translate(100, 200); + if (runDemo[3]) demo3(); + if (runDemo[6]) demo6(); + if (runDemo[7]) demo7(); + if (runDemo[8]) demo8(); + if (runDemo[9]) demo9(); + if (runDemo[0]) demo10(); + } + + //basic self-intersecting polygon + private void demo1() { + if (printDemo) println("demo1"); + + strokeWeight(6 * weight * displayDensity); + stroke(0, 127, 95, 191); + beginShape(); + vertex(100, 200); + vertex(200, 100); + vertex(300, 200); + vertex(400, 100); + vertex(350, 200); + vertex(450, 100); + + vertex(300, 300); + vertex(mouseX, mouseY); + vertex(600, 200); + + vertex(550, 100); + vertex(550, 400); + vertex(750, 400); + vertex(750, 600); + vertex(100, 600); + endShape(mode); + } + + + //mouse controlled polygon + private void demo2() { + if (printDemo) println("demo2"); + + //NOTE: we draw each vertex with a random fill color to test how it behaves. + //in P2D, the colors are interpolated across the triangles output by the GLU tessellator. + //in JAVA2D, when endShape() is called, the currently active color is used for all vertices. + //for now P4D follows the behavior of P2D, but switching to JAVA2D's behavior + //would allow us to simplify our implementation a bit + beginShape(); + for (int i = 0; i < points.size(); ++i) { + fill(c[i]); + vertex(points.get(i).x, points.get(i).y); + } + endShape(); + } + + + //textured polygon + private void demo3() { + if (printDemo) println("demo3"); + + //test that textured shapes and tint() work correctly + float s = 4; + beginShape(); + texture(img); + vertex(10*s, 20*s, 0, 0); + tint(0, 255, 127, 127); + vertex(80*s, 5*s, 800, 0); + vertex(95*s, 90*s, 800, 800); + noTint(); + vertex(40*s, 95*s, 0, 800); + endShape(); + + //test that image() function works correctly + tint(255, 31); + rotate(-1); + image(img, -200, 100); + rotate(1); + tint(255); + image(img, 700, 100, 200, 100); + } + + //text rendering + private void demo4() { + if (printDemo) println("demo4"); + + textFont(font); + text("Now is the time for all good men to come to the aid of their country.\n" + + "If they do not the quick brown fox may never jump over the lazy sleeping dog again.\n" + + "He may, however, take up knitting as a suitable hobby for all retired quick brown foxes.\n" + + "This is test #1 of 9,876,543,210.\n" + + "Collect them all!", 0, 100); + } + + //shapes benchmark + private void demo5() { + if (printDemo) println("demo5"); + + strokeWeight(2 * displayDensity); + stroke(0); + fill(200); + + float dev = 10; //deviation + + //change these parameters to benchmark various things + int unit = 10; + //line, triangle, rect, ellipse, point + int[] amount = { 20, 15, 10, 5, 40 }; + + for (int i = 0; i < amount[0]*unit; ++i) { + float x = random(width); + float y = random(height); + line(x, y, x + random(-dev, dev), y + random(-dev, dev)); + } + + for (int i = 0; i < amount[1]*unit; ++i) { + float x = random(width); + float y = random(height); + triangle(x, y, + x + random(-dev*2, dev*2), y + random(-dev*2, dev*2), + x + random(-dev*2, dev*2), y + random(-dev*2, dev*2)); + } + + for (int i = 0; i < amount[2]*unit; ++i) { + rect(random(width), random(height), random(dev), random(dev)); + } + + for (int i = 0; i < amount[3]*unit; ++i) { + ellipse(random(width), random(height), random(dev*2), random(dev*2)); + } + + for (int i = 0; i < amount[4]*unit; ++i) { + point(random(width), random(height)); + } + + //large ellipse to test smoothness of outline + ellipse(width/2, height/2, width/2, height/4); + } + + //duplicate vertex test + private void demo6() { + if (printDemo) println("demo6"); + + //NOTE: yes, this produces the wrong result in P4D + //see PGraphics4D.shapeVertex() for why + beginShape(); + vertex(500, 300); + vertex(600, 400); //dupe + vertex(700, 300); + vertex(650, 300); + vertex(600, 400); //dupe + vertex(550, 300); + endShape(CLOSE); + } + + //user-define contours + private void demo7() { + if (printDemo) println("demo7"); + + //from https://processing.org/reference/beginContour_.html + + fill(127, 255, 127); + stroke(255, 0, 0); + beginShape(); + // Exterior part of shape, clockwise winding + vertex(-40, -40); + vertex(40, -40); + vertex(40, 40); + vertex(-40, 40); + // Interior part of shape, counter-clockwise winding + beginContour(); + vertex(-20, -20); + vertex(-20, 20); + vertex(20, 20); + vertex(20, -20); + endContour(); + endShape(CLOSE); + } + + //primitive types + private void demo8() { + if (printDemo) println("demo8"); + + //from https://processing.org/reference/beginShape_.html + + stroke(0); + strokeWeight(4 * displayDensity); + fill(255, 127, 127); + + pushMatrix(); + resetMatrix(); + scale(2); + + beginShape(); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(CLOSE); + + translate(100, 0); + + beginShape(POINTS); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(); + + translate(100, 0); + + beginShape(LINES); + vertex(30, 40); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(); + + translate(100, 0); + + pushStyle(); + noFill(); + beginShape(); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(); + popStyle(); + + translate(100, 0); + + pushStyle(); + noFill(); + beginShape(); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(CLOSE); + popStyle(); + + translate(100, 0); + + beginShape(TRIANGLES); + vertex(30, 75); + vertex(40, 20); + vertex(50, 75); + vertex(60, 20); + vertex(70, 75); + vertex(80, 20); + endShape(); + + resetMatrix(); + scale(2); + translate(0, 100); + + beginShape(TRIANGLE_STRIP); + vertex(30, 75); + vertex(40, 20); + vertex(50, 75); + vertex(60, 20); + vertex(70, 75); + vertex(80, 20); + vertex(90, 75); + endShape(); + + translate(100, 0); + + beginShape(TRIANGLE_FAN); + vertex(57.5f, 50); + vertex(57.5f, 15); + vertex(92, 50); + vertex(57.5f, 85); + vertex(22, 50); + vertex(57.5f, 15); + endShape(); + + translate(100, 0); + + beginShape(QUADS); + vertex(30, 20); + vertex(30, 75); + vertex(50, 75); + vertex(50, 20); + vertex(65, 20); + vertex(65, 75); + vertex(85, 75); + vertex(85, 20); + endShape(); + + translate(100, 0); + + beginShape(QUAD_STRIP); + vertex(30, 20); + vertex(30, 75); + vertex(50, 20); + vertex(50, 75); + vertex(65, 20); + vertex(65, 75); + vertex(85, 20); + vertex(85, 75); + endShape(); + + translate(100, 0); + + beginShape(); + vertex(20, 20); + vertex(40, 20); + vertex(40, 40); + vertex(60, 40); + vertex(60, 60); + vertex(20, 60); + endShape(CLOSE); + + //test handling of concave and self-intersecting quads + //NOTE: JAVA2D currently draws these correctly, but P2D does not + resetMatrix(); + scale(2); + translate(0, 200); + strokeWeight(2 * displayDensity); + float t = frameCount * 0.01f; + + beginShape(QUADS); + vertex(50, 10); + vertex(90, 50); + vertex(30 + 20*sin(t), 70 + 20*cos(t)); + vertex(30 - 20*sin(t), 70 - 20*cos(t)); + endShape(CLOSE); + + translate(100, 0); + + beginShape(QUAD_STRIP); + vertex(50, 10); + vertex(90, 50); + vertex(30 + 20*sin(t), 70 + 20*cos(t)); + vertex(30 - 20*sin(t), 70 - 20*cos(t)); + endShape(CLOSE); + + popMatrix(); + } + + //testing angular stuff + private void demo9() { + if (printDemo) println("demo9"); + + strokeWeight(4 * displayDensity); + stroke(127, 0, 0); + fill(255, 255, 255); + + //testing the behavior of floating point % operator (for dealing with angles) + float py = 0; + for (int i = 0; i < width; ++i) { + float x = (i - width/2) * 0.1f; + float y = height/2 - (x % PI) * 10; + line(i, y, i - 1, py); + py = y; + } + + //testing the behavior of P2D arc() at various angles + //NOTE: arcs with negative angle aren't drawn + arc(100, 100, 100, 100, -1, new PVector(mouseX, mouseY).sub(100, 100).heading()); + + //test for whether LINES primitive type has self-overlap + //NOTE: it does in JAVA2D, but not in P2D + stroke(0, 127, 127, 127); + beginShape(LINES); + vertex(0, 0); + vertex(width, height + 100); + vertex(width, 0); + vertex(0, height + 100); + endShape(); + } + + //curve tests + private void demo10() { + if (printDemo) println("demo10"); + + //these cause errors in P4D because we haven't implemented them yet + //so they're disabled in the demo for now + if (getGraphics() instanceof PGraphics2DX) { + return; + } + + noFill(); + stroke(0); + strokeWeight(4 * displayDensity); + pushMatrix(); + scale(2); + + beginShape(); + curveVertex(84, 91); + curveVertex(84, 91); + curveVertex(68, 19); + curveVertex(21, 17); + curveVertex(32, 100); + curveVertex(32, 100); + endShape(); + + translate(100, 0); + + beginShape(); + vertex(30, 20); + bezierVertex(80, 0, 80, 75, 30, 75); + bezierVertex(50, 80, 60, 25, 30, 20); + endShape(); + + translate(100, 0); + + beginShape(); + vertex(20, 20); + quadraticVertex(80, 20, 50, 50); + quadraticVertex(20, 80, 80, 80); + vertex(80, 60); + endShape(); + + popMatrix(); + } + + public void mousePressed() { + if (keyboard) { + closeKeyboard(); + keyboard = false; + } else { + if (0.9 * height < mouseY) { + openKeyboard(); + keyboard = true; + } else { + // behavior for demo 2 + points.add(new PVector(mouseX, mouseY)); + } + } + } + + public void mouseDragged() { + if ( mouseY < 0.1 * height) { + points.get(points.size() - 1).x = mouseX; + points.get(points.size() - 1).y = mouseY; + } + } + + public void keyPressed() { + if (key == 'q') { + join = MITER; + } else if (key == 'w') { + join = BEVEL; + } else if (key == 'e') { + join = ROUND; + } else if (key == 'a') { + cap = SQUARE; + } else if (key == 's') { + cap = PROJECT; + } else if (key == 'd') { + cap = ROUND; + } else if (key == 'r') { + mode = OPEN; + } else if (key == 'f') { + mode = CLOSE; + } else if (key == 't') { +// PGraphics2DX.premultiplyMatrices = true; + } else if (key == 'g') { +// PGraphics2DX.premultiplyMatrices = false; + } else if (key == ' ') { +// PJOGL pgl = (PJOGL)((PGraphics2D)this.g).pgl; +// if (wireframe) +// pgl.gl.getGL4().glPolygonMode(GL4.GL_FRONT_AND_BACK, GL4.GL_FILL); +// else +// pgl.gl.getGL4().glPolygonMode(GL4.GL_FRONT_AND_BACK, GL4.GL_LINE); +// wireframe = !wireframe; + } else if (key - '0' >= 0 && key - '0' < 10) { + runDemo[key - '0'] = !runDemo[key - '0']; + } + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchArcTest.java b/apps/fast2d/src/main/java/fast2d/SketchArcTest.java new file mode 100644 index 000000000..d873db96c --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchArcTest.java @@ -0,0 +1,52 @@ +package fast2d; + +import processing.core.PApplet; +import processing.core.PVector; + +public class SketchArcTest extends PApplet { + float weight = 1; + + int join = MITER; + int cap = SQUARE; + int mode = OPEN; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + strokeWeight(4 * displayDensity); + stroke(127, 0, 0); + fill(255, 255, 255); + + //testing the behavior of floating point % operator (for dealing with angles) + float py = 0; + for (int i = 0; i < width; ++i) { + float x = (i - width/2) * 0.1f; + float y = height/2 - (x % PI) * 10; + line(i, y, i - 1, py); + py = y; + } + + //testing the behavior of P2D arc() at various angles + //NOTE: arcs with negative angle aren't drawn + arc(100, 100, 100, 100, -1, new PVector(mouseX, mouseY).sub(100, 100).heading()); + + //test for whether LINES primitive type has self-overlap + //NOTE: it does in JAVA2D, but not in P2D + stroke(0, 127, 127, 127); + beginShape(LINES); + vertex(0, 0); + vertex(width, height + 100); + vertex(width, 0); + vertex(0, height + 100); + endShape(); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchBasicPoly.java b/apps/fast2d/src/main/java/fast2d/SketchBasicPoly.java new file mode 100644 index 000000000..2d92ed81b --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchBasicPoly.java @@ -0,0 +1,48 @@ +package fast2d; + +import processing.core.PApplet; + +public class SketchBasicPoly extends PApplet { + float weight = 1; + + int join = MITER; + int cap = SQUARE; + int mode = OPEN; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + fill(255, 0, 63, 127); + translate(100, 200); + + strokeWeight(6 * weight * displayDensity); + stroke(0, 127, 95, 191); + beginShape(); + vertex(100, 200); + vertex(200, 100); + vertex(300, 200); + vertex(400, 100); + vertex(350, 200); + vertex(450, 100); + + vertex(300, 300); + vertex(mouseX, mouseY); + vertex(600, 200); + + vertex(550, 100); + vertex(550, 400); + vertex(750, 400); + vertex(750, 600); + vertex(100, 600); + endShape(mode); + } +} \ No newline at end of file diff --git a/apps/fast2d/src/main/java/fast2d/SketchCurveTest.java b/apps/fast2d/src/main/java/fast2d/SketchCurveTest.java new file mode 100644 index 000000000..55c1b391a --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchCurveTest.java @@ -0,0 +1,62 @@ +package fast2d; + +import processing.core.PApplet; +import processing.opengl.PGraphics2DX; + +public class SketchCurveTest extends PApplet { + int join = MITER; + int cap = SQUARE; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + //these cause errors in P4D because we haven't implemented them yet + //so they're disabled in the demo for now + if (getGraphics() instanceof PGraphics2DX) { + return; + } + + noFill(); + stroke(0); + strokeWeight(4 * displayDensity); + pushMatrix(); + scale(2); + + beginShape(); + curveVertex(84, 91); + curveVertex(84, 91); + curveVertex(68, 19); + curveVertex(21, 17); + curveVertex(32, 100); + curveVertex(32, 100); + endShape(); + + translate(100, 0); + + beginShape(); + vertex(30, 20); + bezierVertex(80, 0, 80, 75, 30, 75); + bezierVertex(50, 80, 60, 25, 30, 20); + endShape(); + + translate(100, 0); + + beginShape(); + vertex(20, 20); + quadraticVertex(80, 20, 50, 50); + quadraticVertex(20, 80, 80, 80); + vertex(80, 60); + endShape(); + + popMatrix(); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchCustomShader.java b/apps/fast2d/src/main/java/fast2d/SketchCustomShader.java new file mode 100644 index 000000000..d8036d06e --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchCustomShader.java @@ -0,0 +1,36 @@ +package fast2d; + +import processing.core.PApplet; +import processing.core.PImage; +import processing.opengl.PShader; + +public class SketchCustomShader extends PApplet { + PShader edges; + PImage img; + boolean enabled = true; + + public void settings() { +// fullScreen(P2D); + fullScreen(P2DX); + } + + public void setup() { + orientation(LANDSCAPE); + img = loadImage("leaves.jpg"); + edges = loadShader("edges.glsl"); + } + + public void draw() { + if (enabled == true) { + shader(edges); + } + image(img, 0, 0, width, height); + } + + public void mousePressed() { + enabled = !enabled; + if (!enabled) { + resetShader(); + } + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchDisplayText.java b/apps/fast2d/src/main/java/fast2d/SketchDisplayText.java new file mode 100644 index 000000000..b79c08b02 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchDisplayText.java @@ -0,0 +1,27 @@ +package fast2d; + +import processing.core.PApplet; +import processing.core.PFont; + +public class SketchDisplayText extends PApplet { + PFont font; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + font = createFont("SansSerif", displayDensity * 72); + } + + public void draw() { + background(255); + + textFont(font); + text("Now is the time for all good men to come to the aid of their country.\n" + + "If they do not the quick brown fox may never jump over the lazy sleeping dog again.\n" + + "He may, however, take up knitting as a suitable hobby for all retired quick brown foxes.\n" + + "This is test #1 of 9,876,543,210.\n" + + "Collect them all!", 0, 100); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchDuplicatedVert.java b/apps/fast2d/src/main/java/fast2d/SketchDuplicatedVert.java new file mode 100644 index 000000000..f3093f81d --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchDuplicatedVert.java @@ -0,0 +1,32 @@ +package fast2d; + +import processing.core.PApplet; + +public class SketchDuplicatedVert extends PApplet { + int join = MITER; + int cap = SQUARE; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + //NOTE: yes, this produces the wrong result in P4D + //see PGraphics4D.shapeVertex() for why + beginShape(); + vertex(500, 300); + vertex(600, 400); //dupe + vertex(700, 300); + vertex(650, 300); + vertex(600, 400); //dupe + vertex(550, 300); + endShape(CLOSE); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchFilterTest.java b/apps/fast2d/src/main/java/fast2d/SketchFilterTest.java new file mode 100644 index 000000000..3b73ccc83 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchFilterTest.java @@ -0,0 +1,27 @@ +package fast2d; + +import processing.core.PApplet; +import processing.opengl.PShader; + +public class SketchFilterTest extends PApplet { + PShader blur; + + public void settings() { +// fullScreen(P2D); + fullScreen(P2DX); + } + + public void setup() { +// orientation(LANDSCAPE); + blur = loadShader("blur.glsl"); + stroke(255, 0, 0); + rectMode(CENTER); + strokeWeight(5 * displayDensity); + } + + public void draw() { + filter(blur); + rect(mouseX, mouseY, 150, 150); + ellipse(mouseX, mouseY, 100, 100); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchLoadDisplaySVG.java b/apps/fast2d/src/main/java/fast2d/SketchLoadDisplaySVG.java new file mode 100644 index 000000000..aa146fb59 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchLoadDisplaySVG.java @@ -0,0 +1,22 @@ +package fast2d; + +import processing.core.PApplet; +import processing.core.PShape; + +public class SketchLoadDisplaySVG extends PApplet { + PShape bot; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + bot = loadShape("bot1.svg"); + } + + public void draw() { + background(102); + shape(bot, 110, 90, 100, 100); // Draw at coordinate (110, 90) at size 100 x 100 + shape(bot, 280, 40); // Draw at coordinate (280, 40) at the default size + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchMousePoly.java b/apps/fast2d/src/main/java/fast2d/SketchMousePoly.java new file mode 100644 index 000000000..39e5e9a15 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchMousePoly.java @@ -0,0 +1,52 @@ +package fast2d; + +import java.util.ArrayList; + +import processing.core.PApplet; +import processing.core.PVector; + +public class SketchMousePoly extends PApplet { + float weight = 1; + + //data for demo 2 + int[] c = new int[4096]; + ArrayList points = new ArrayList(); + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + //setup for demo 2 + for (int i = 0; i < c.length; ++i) { + c[i] = color(random(255), random(255), random(255)); + } + } + + public void draw() { + background(255); + + noStroke(); + + //NOTE: we draw each vertex with a random fill color to test how it behaves. + //in P2D, the colors are interpolated across the triangles output by the GLU tessellator. + //in JAVA2D, when endShape() is called, the currently active color is used for all vertices. + //for now P4D follows the behavior of P2D, but switching to JAVA2D's behavior + //would allow us to simplify our implementation a bit + beginShape(); + for (int i = 0; i < points.size(); ++i) { + fill(c[i]); + vertex(points.get(i).x, points.get(i).y); + } + endShape(); + } + + public void mousePressed() { + points.add(new PVector(mouseX, mouseY)); + } + + public void mouseDragged() { + points.get(points.size() - 1).x = mouseX; + points.get(points.size() - 1).y = mouseY; + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchPrimitiveTypes.java b/apps/fast2d/src/main/java/fast2d/SketchPrimitiveTypes.java new file mode 100644 index 000000000..b14593c31 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchPrimitiveTypes.java @@ -0,0 +1,182 @@ +package fast2d; + +import processing.core.PApplet; + +public class SketchPrimitiveTypes extends PApplet { + float weight = 1; + + int join = MITER; + int cap = SQUARE; + int mode = OPEN; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + //from https://processing.org/reference/beginShape_.html + + stroke(0); + strokeWeight(4 * displayDensity); + fill(255, 127, 127); + + pushMatrix(); + resetMatrix(); + scale(2); + + beginShape(); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(CLOSE); + + translate(100, 0); + + beginShape(POINTS); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(); + + translate(100, 0); + + beginShape(LINES); + vertex(30, 40); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(); + + translate(100, 0); + + pushStyle(); + noFill(); + beginShape(); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(); + popStyle(); + + translate(100, 0); + + pushStyle(); + noFill(); + beginShape(); + vertex(30, 20); + vertex(85, 20); + vertex(85, 75); + vertex(30, 75); + endShape(CLOSE); + popStyle(); + + translate(100, 0); + + beginShape(TRIANGLES); + vertex(30, 75); + vertex(40, 20); + vertex(50, 75); + vertex(60, 20); + vertex(70, 75); + vertex(80, 20); + endShape(); + + resetMatrix(); + scale(2); + translate(0, 100); + + beginShape(TRIANGLE_STRIP); + vertex(30, 75); + vertex(40, 20); + vertex(50, 75); + vertex(60, 20); + vertex(70, 75); + vertex(80, 20); + vertex(90, 75); + endShape(); + + translate(100, 0); + + beginShape(TRIANGLE_FAN); + vertex(57.5f, 50); + vertex(57.5f, 15); + vertex(92, 50); + vertex(57.5f, 85); + vertex(22, 50); + vertex(57.5f, 15); + endShape(); + + translate(100, 0); + + beginShape(QUADS); + vertex(30, 20); + vertex(30, 75); + vertex(50, 75); + vertex(50, 20); + vertex(65, 20); + vertex(65, 75); + vertex(85, 75); + vertex(85, 20); + endShape(); + + translate(100, 0); + + beginShape(QUAD_STRIP); + vertex(30, 20); + vertex(30, 75); + vertex(50, 20); + vertex(50, 75); + vertex(65, 20); + vertex(65, 75); + vertex(85, 20); + vertex(85, 75); + endShape(); + + translate(100, 0); + + beginShape(); + vertex(20, 20); + vertex(40, 20); + vertex(40, 40); + vertex(60, 40); + vertex(60, 60); + vertex(20, 60); + endShape(CLOSE); + + //test handling of concave and self-intersecting quads + //NOTE: JAVA2D currently draws these correctly, but P2D does not + resetMatrix(); + scale(2); + translate(0, 200); + strokeWeight(2 * displayDensity); + float t = frameCount * 0.01f; + + beginShape(QUADS); + vertex(50, 10); + vertex(90, 50); + vertex(30 + 20*sin(t), 70 + 20*cos(t)); + vertex(30 - 20*sin(t), 70 - 20*cos(t)); + endShape(CLOSE); + + translate(100, 0); + + beginShape(QUAD_STRIP); + vertex(50, 10); + vertex(90, 50); + vertex(30 + 20*sin(t), 70 + 20*cos(t)); + vertex(30 - 20*sin(t), 70 - 20*cos(t)); + endShape(CLOSE); + + popMatrix(); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchShaderNoTex.java b/apps/fast2d/src/main/java/fast2d/SketchShaderNoTex.java new file mode 100644 index 000000000..5abfb4c70 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchShaderNoTex.java @@ -0,0 +1,24 @@ +package fast2d; + +import processing.core.PApplet; +import processing.opengl.PShader; + +public class SketchShaderNoTex extends PApplet { + PShader sh; + + public void settings() { +// fullScreen(P2D); + fullScreen(P2DX); + } + + public void setup() { + orientation(LANDSCAPE); + sh = loadShader("frag.glsl", "vert.glsl"); + shader(sh); + } + + public void draw() { + translate(mouseX, mouseY); + rect(0, 0, 400, 400); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchShapeBenchmark.java b/apps/fast2d/src/main/java/fast2d/SketchShapeBenchmark.java new file mode 100644 index 000000000..8f21dae08 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchShapeBenchmark.java @@ -0,0 +1,70 @@ +package fast2d; + +import processing.core.PApplet; +import processing.opengl.PGraphics2DX; + +public class SketchShapeBenchmark extends PApplet { + int join = MITER; + int cap = SQUARE; + + boolean premultiply = true; + + float dev = 10; //deviation + + //change these parameters to benchmark various things + int unit = 10; + //line, triangle, rect, ellipse, point + int[] amount = { 20, 15, 10, 5, 40 }; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); +// PGraphics2DX.premultiplyMatrices = premultiply; + + textFont(createFont("SansSerif", 15 * displayDensity)); + } + + public void draw() { + background(255); + + strokeWeight(2 * displayDensity); + stroke(0); + fill(200); + + for (int i = 0; i < amount[0]*unit; ++i) { + float x = random(width); + float y = random(height); + line(x, y, x + random(-dev, dev), y + random(-dev, dev)); + } + + for (int i = 0; i < amount[1]*unit; ++i) { + float x = random(width); + float y = random(height); + triangle(x, y, + x + random(-dev*2, dev*2), y + random(-dev*2, dev*2), + x + random(-dev*2, dev*2), y + random(-dev*2, dev*2)); + } + + for (int i = 0; i < amount[2]*unit; ++i) { + rect(random(width), random(height), random(dev), random(dev)); + } + + for (int i = 0; i < amount[3]*unit; ++i) { + ellipse(random(width), random(height), random(dev*2), random(dev*2)); + } + + for (int i = 0; i < amount[4]*unit; ++i) { + point(random(width), random(height)); + } + + //large ellipse to test smoothness of outline + ellipse(width/2, height/2, width/2, height/4); + + fill(255, 0, 0); + text((int) frameRate + " fps", 30, 30); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchTexturedPoly.java b/apps/fast2d/src/main/java/fast2d/SketchTexturedPoly.java new file mode 100644 index 000000000..97d8ff292 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchTexturedPoly.java @@ -0,0 +1,47 @@ +package fast2d; + +import processing.core.PApplet; +import processing.core.PImage; + +public class SketchTexturedPoly extends PApplet { + PImage img; + + int join = MITER; + int cap = SQUARE; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + img = loadImage("leaves.jpg"); + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + translate(100, 200); + + //test that textured shapes and tint() work correctly + float s = 4; + beginShape(); + texture(img); + vertex(10*s, 20*s, 0, 0); + tint(0, 255, 127, 127); + vertex(80*s, 5*s, 800, 0); + vertex(95*s, 90*s, 800, 800); + noTint(); + vertex(40*s, 95*s, 0, 800); + endShape(); + + //test that image() function works correctly + tint(255, 31); + rotate(-1); + image(img, -200, 100); + rotate(1); + tint(255); + image(img, 700, 100, 200, 100); + } +} diff --git a/apps/fast2d/src/main/java/fast2d/SketchUserDefinedContours.java b/apps/fast2d/src/main/java/fast2d/SketchUserDefinedContours.java new file mode 100644 index 000000000..1b4635cc3 --- /dev/null +++ b/apps/fast2d/src/main/java/fast2d/SketchUserDefinedContours.java @@ -0,0 +1,41 @@ +package fast2d; + +import processing.core.PApplet; + +public class SketchUserDefinedContours extends PApplet { + + int join = MITER; + int cap = SQUARE; + + public void settings() { + fullScreen(P2DX); + } + + public void setup() { + strokeCap(cap); + strokeJoin(join); + } + + public void draw() { + background(255); + + //from https://processing.org/reference/beginContour_.html + + fill(127, 255, 127); + stroke(255, 0, 0); + beginShape(); + // Exterior part of shape, clockwise winding + vertex(-40, -40); + vertex(40, -40); + vertex(40, 40); + vertex(-40, 40); + // Interior part of shape, counter-clockwise winding + beginContour(); + vertex(-20, -20); + vertex(-20, 20); + vertex(20, 20); + vertex(20, -20); + endContour(); + endShape(CLOSE); + } +} diff --git a/apps/fast2d/src/main/res/layout/activity_main.xml b/apps/fast2d/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..1f9d42012 --- /dev/null +++ b/apps/fast2d/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/apps/fast2d/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/fast2d/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/fast2d/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/fast2d/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/fast2d/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/fast2d/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/fast2d/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/fast2d/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/fast2d/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/fast2d/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/fast2d/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/fast2d/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/fast2d/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/fast2d/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aee44e138 Binary files /dev/null and b/apps/fast2d/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/fast2d/src/main/res/values-w820dp/dimens.xml b/apps/fast2d/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 000000000..63fc81644 --- /dev/null +++ b/apps/fast2d/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/apps/fast2d/src/main/res/values/colors.xml b/apps/fast2d/src/main/res/values/colors.xml new file mode 100644 index 000000000..3ab3e9cbc --- /dev/null +++ b/apps/fast2d/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/apps/fast2d/src/main/res/values/dimens.xml b/apps/fast2d/src/main/res/values/dimens.xml new file mode 100644 index 000000000..47c822467 --- /dev/null +++ b/apps/fast2d/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/apps/fast2d/src/main/res/values/strings.xml b/apps/fast2d/src/main/res/values/strings.xml new file mode 100644 index 000000000..44978fbbe --- /dev/null +++ b/apps/fast2d/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Fast 2D + diff --git a/apps/fast2d/src/main/res/values/styles.xml b/apps/fast2d/src/main/res/values/styles.xml new file mode 100644 index 000000000..5885930df --- /dev/null +++ b/apps/fast2d/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/apps/simple/build.gradle b/apps/simple/build.gradle new file mode 100644 index 000000000..6310d85d3 --- /dev/null +++ b/apps/simple/build.gradle @@ -0,0 +1,35 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'processing.tests.simple' + compileSdkVersion 33 + + defaultConfig { + applicationId "processing.tests.simple" + minSdkVersion 17 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + testImplementation 'junit:junit:4.13.2' + implementation 'androidx.appcompat:appcompat:1.6.1' + + implementation project(':libs:processing-core') +} diff --git a/apps/simple/proguard-rules.pro b/apps/simple/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/apps/simple/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/apps/simple/src/main/AndroidManifest.xml b/apps/simple/src/main/AndroidManifest.xml new file mode 100644 index 000000000..f4ca7c1cd --- /dev/null +++ b/apps/simple/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/simple/src/main/assets/leaf.png b/apps/simple/src/main/assets/leaf.png new file mode 100644 index 000000000..61ca00df8 Binary files /dev/null and b/apps/simple/src/main/assets/leaf.png differ diff --git a/apps/simple/src/main/java/simple/MainActivity.java b/apps/simple/src/main/java/simple/MainActivity.java new file mode 100644 index 000000000..7328c802f --- /dev/null +++ b/apps/simple/src/main/java/simple/MainActivity.java @@ -0,0 +1,60 @@ +package simple; + +import android.os.Bundle; +import android.content.Intent; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.appcompat.app.AppCompatActivity; + +import processing.android.PFragment; +import processing.android.CompatUtils; +import processing.core.PApplet; + +public class MainActivity extends AppCompatActivity { + private PApplet sketch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FrameLayout frame = new FrameLayout(this); + frame.setId(CompatUtils.getUniqueViewId()); + setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + sketch = new Sketch(); + PFragment fragment = new PFragment(sketch); + fragment.setView(frame, this); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (sketch != null) { + sketch.onRequestPermissionsResult( + requestCode, permissions, grantResults); + } + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } +} diff --git a/apps/simple/src/main/java/simple/Sketch.java b/apps/simple/src/main/java/simple/Sketch.java new file mode 100644 index 000000000..436ff3f94 --- /dev/null +++ b/apps/simple/src/main/java/simple/Sketch.java @@ -0,0 +1,23 @@ +package simple; + +import processing.core.PApplet; +import processing.core.PImage; + +public class Sketch extends PApplet { + + PImage leaf; + + public void settings() { + fullScreen(); + } + + public void setup() { + leaf = loadImage("leaf.png"); + imageMode(CENTER); + } + + public void draw() { + background(9); + image(leaf, mouseX, mouseY); + } +} \ No newline at end of file diff --git a/apps/simple/src/main/res/layout/activity_main.xml b/apps/simple/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..1f9d42012 --- /dev/null +++ b/apps/simple/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/apps/simple/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/simple/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/simple/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/simple/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/simple/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/simple/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/simple/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/simple/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/simple/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/simple/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/simple/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/simple/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aee44e138 Binary files /dev/null and b/apps/simple/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/simple/src/main/res/values-w820dp/dimens.xml b/apps/simple/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 000000000..63fc81644 --- /dev/null +++ b/apps/simple/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/apps/simple/src/main/res/values/colors.xml b/apps/simple/src/main/res/values/colors.xml new file mode 100644 index 000000000..3ab3e9cbc --- /dev/null +++ b/apps/simple/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/apps/simple/src/main/res/values/dimens.xml b/apps/simple/src/main/res/values/dimens.xml new file mode 100644 index 000000000..47c822467 --- /dev/null +++ b/apps/simple/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ + + + 16dp + 16dp + diff --git a/apps/simple/src/main/res/values/strings.xml b/apps/simple/src/main/res/values/strings.xml new file mode 100644 index 000000000..5f98a3846 --- /dev/null +++ b/apps/simple/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Simple Test + diff --git a/apps/simple/src/main/res/values/styles.xml b/apps/simple/src/main/res/values/styles.xml new file mode 100644 index 000000000..5885930df --- /dev/null +++ b/apps/simple/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/apps/vrcube/build.gradle b/apps/vrcube/build.gradle new file mode 100644 index 000000000..b062a918f --- /dev/null +++ b/apps/vrcube/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'com.android.application' +} + +android { + defaultConfig { + applicationId "processing.tests.vrcube" + minSdkVersion 19 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + namespace 'vrcube' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + implementation project(':libs:processing-core') + implementation project(':libs:google-vr') + implementation project(':libs:processing-vr') + implementation 'com.google.protobuf.nano:protobuf-javanano:3.1.0' + implementation 'androidx.appcompat:appcompat:1.6.0' +} diff --git a/apps/vrcube/gradle.properties b/apps/vrcube/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/apps/vrcube/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/apps/vrcube/src/main/AndroidManifest.xml b/apps/vrcube/src/main/AndroidManifest.xml new file mode 100644 index 000000000..03513aece --- /dev/null +++ b/apps/vrcube/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/vrcube/src/main/java/vrcube/MainActivity.java b/apps/vrcube/src/main/java/vrcube/MainActivity.java new file mode 100644 index 000000000..22ef6a835 --- /dev/null +++ b/apps/vrcube/src/main/java/vrcube/MainActivity.java @@ -0,0 +1,23 @@ +package vrcube; + +import android.os.Build; +import android.os.Bundle; +import android.view.WindowManager; + +import processing.vr.VRActivity; +import processing.core.PApplet; + +public class MainActivity extends VRActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + + PApplet sketch = new Sketch(); + setSketch(sketch); + } +} \ No newline at end of file diff --git a/apps/vrcube/src/main/java/vrcube/Sketch.java b/apps/vrcube/src/main/java/vrcube/Sketch.java new file mode 100644 index 000000000..5c7ee3f59 --- /dev/null +++ b/apps/vrcube/src/main/java/vrcube/Sketch.java @@ -0,0 +1,181 @@ +package vrcube; + +import processing.core.PApplet; +import processing.core.PMatrix3D; +import processing.core.PVector; +import processing.vr.*; + +public class Sketch extends PApplet { + float boxSize = 140; + VRCamera vrcam; + Selector vrsel; + + public void settings() { + fullScreen(VR); + } + + public void setup() { + vrcam = new VRCamera(this); + vrsel = new Selector(this); +// vrcam.setNear(1000); +// vrcam.setFar(1100); + } + + public void draw() { + vrsel.update(); + background(120); + translate(width/2, height/2); + lights(); + drawGrid(); + drawAim(); + } + + void drawGrid() { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + float x = map(i, 0, 3, -350, +350); + float y = map(j, 0, 3, -350, +350); + pushMatrix(); + translate(x, y); + rotateY(millis()/1000.0f); + if (vrsel.hit(boxSize)) { + strokeWeight(5); + stroke(0xFF2FB1EA); + if (mousePressed) { + fill(0xFF2FB1EA); + } else { + fill(0xFFE3993E); + } + } else { + noStroke(); + fill(0xFFE3993E); + } + box(boxSize); + popMatrix(); + } + } + } + + void drawAim() { + vrcam.sticky(); + stroke(47, 177, 234, 150); + strokeWeight(50); + point(0, 0, 100); + vrcam.noSticky(); + } + + class Selector { + protected PApplet parent; + + protected PVector dir = new PVector(); + protected PVector cam = new PVector(); + + protected PMatrix3D eyeMat = new PMatrix3D(); + protected PMatrix3D objMat = new PMatrix3D(); + + protected PVector front = new PVector(); + protected PVector objCam = new PVector(); + protected PVector objFront = new PVector(); + protected PVector objDir = new PVector(); + + protected PVector hit = new PVector(); + + public Selector(PApplet parent) { + this.parent = parent; + } + + public void update() { + parent.getEyeMatrix(eyeMat); + cam.set(eyeMat.m03, eyeMat.m13, eyeMat.m23); + dir.set(eyeMat.m02, eyeMat.m12, eyeMat.m22); + PVector.add(cam, dir, front); + } + + public boolean hit(PMatrix3D mat, float boxSize) { + objMat.set(mat); + return hitImpl(boxSize); + } + + public boolean hit(float boxSize) { + parent.getObjectMatrix(objMat); + return hitImpl(boxSize); + } + + protected boolean hitImpl(float boxSize) { + objMat.mult(cam, objCam); + objMat.mult(front, objFront); + PVector.sub(objFront, objCam, objDir); + PVector boxMin = new PVector(-boxSize/2, -boxSize/2, -boxSize/2); + PVector boxMax = new PVector(+boxSize/2, +boxSize/2, +boxSize/2); + return intersectsLine(objCam, objDir, boxMin, boxMax, 0, 1000, hit); + } + + protected boolean intersectsLine(PVector orig, PVector dir, + PVector minPos, PVector maxPos, float minDist, float maxDist, PVector hit) { + PVector bbox; + PVector invDir = new PVector(1/dir.x, 1/dir.y, 1/dir.z); + + boolean signDirX = invDir.x < 0; + boolean signDirY = invDir.y < 0; + boolean signDirZ = invDir.z < 0; + + bbox = signDirX ? maxPos : minPos; + float txmin = (bbox.x - orig.x) * invDir.x; + bbox = signDirX ? minPos : maxPos; + float txmax = (bbox.x - orig.x) * invDir.x; + bbox = signDirY ? maxPos : minPos; + float tymin = (bbox.y - orig.y) * invDir.y; + bbox = signDirY ? minPos : maxPos; + float tymax = (bbox.y - orig.y) * invDir.y; + + if ((txmin > tymax) || (tymin > txmax)) { + return false; + } + if (tymin > txmin) { + txmin = tymin; + } + if (tymax < txmax) { + txmax = tymax; + } + + bbox = signDirZ ? maxPos : minPos; + float tzmin = (bbox.z - orig.z) * invDir.z; + bbox = signDirZ ? minPos : maxPos; + float tzmax = (bbox.z - orig.z) * invDir.z; + + if ((txmin > tzmax) || (tzmin > txmax)) { + return false; + } + if (tzmin > txmin) { + txmin = tzmin; + } + if (tzmax < txmax) { + txmax = tzmax; + } + if ((txmin < maxDist) && (txmax > minDist)) { + hit.x = orig.x + txmin * dir.x; + hit.y = orig.y + txmin * dir.y; + hit.z = orig.z + txmin * dir.z; + return true; + } + return false; + } + } + +/* + public void settings() { + fullScreen(STEREO); + } + + public void setup() { } + + public void draw() { + background(157); + lights(); + translate(width / 2, height / 2); + rotateX(frameCount * 0.01f); + rotateY(frameCount * 0.01f); + box(350); + } +*/ +} diff --git a/apps/vrcube/src/main/res/layout/main.xml b/apps/vrcube/src/main/res/layout/main.xml new file mode 100644 index 000000000..4b602d5f6 --- /dev/null +++ b/apps/vrcube/src/main/res/layout/main.xml @@ -0,0 +1,7 @@ + diff --git a/apps/vrcube/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/vrcube/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/vrcube/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/vrcube/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/vrcube/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/vrcube/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/vrcube/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/vrcube/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/vrcube/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/vrcube/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/vrcube/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/vrcube/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/vrcube/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/vrcube/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aee44e138 Binary files /dev/null and b/apps/vrcube/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/vrcube/src/main/res/values/strings.xml b/apps/vrcube/src/main/res/values/strings.xml new file mode 100644 index 000000000..db8a2b518 --- /dev/null +++ b/apps/vrcube/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + VR Test + diff --git a/apps/vrcube/src/main/res/values/styles.xml b/apps/vrcube/src/main/res/values/styles.xml new file mode 100644 index 000000000..9f408b9b2 --- /dev/null +++ b/apps/vrcube/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + diff --git a/apps/wallpaper/build.gradle b/apps/wallpaper/build.gradle new file mode 100644 index 000000000..aec131e52 --- /dev/null +++ b/apps/wallpaper/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'com.android.application' +} + +android { + defaultConfig { + applicationId "processing.tests.wallpaper" + minSdkVersion 17 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + namespace 'wallpaper' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + implementation project(':libs:processing-core') + implementation 'androidx.appcompat:appcompat:1.6.0' +} \ No newline at end of file diff --git a/apps/wallpaper/gradle.properties b/apps/wallpaper/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/apps/wallpaper/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/apps/wallpaper/src/main/AndroidManifest.xml b/apps/wallpaper/src/main/AndroidManifest.xml new file mode 100644 index 000000000..37695c4da --- /dev/null +++ b/apps/wallpaper/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + diff --git a/apps/wallpaper/src/main/java/wallpaper/DebuggerEntryPointActivity.java b/apps/wallpaper/src/main/java/wallpaper/DebuggerEntryPointActivity.java new file mode 100644 index 000000000..ca80b0079 --- /dev/null +++ b/apps/wallpaper/src/main/java/wallpaper/DebuggerEntryPointActivity.java @@ -0,0 +1,12 @@ +package wallpaper; + +import android.app.Activity; +import android.os.Bundle; +import androidx.annotation.Nullable; + +public class DebuggerEntryPointActivity extends Activity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} diff --git a/apps/wallpaper/src/main/java/wallpaper/MainService.java b/apps/wallpaper/src/main/java/wallpaper/MainService.java new file mode 100644 index 000000000..48a61388f --- /dev/null +++ b/apps/wallpaper/src/main/java/wallpaper/MainService.java @@ -0,0 +1,14 @@ +package wallpaper; + +import processing.android.PWallpaper; +import processing.core.PApplet; + +public class MainService extends PWallpaper { + @Override + public PApplet createSketch() { + // Uncomment the following line when debugging: +// android.os.Debug.waitForDebugger(); + PApplet sketch = new Sketch(); + return sketch; + } +} diff --git a/apps/wallpaper/src/main/java/wallpaper/Sketch.java b/apps/wallpaper/src/main/java/wallpaper/Sketch.java new file mode 100644 index 000000000..a5188d848 --- /dev/null +++ b/apps/wallpaper/src/main/java/wallpaper/Sketch.java @@ -0,0 +1,41 @@ +package wallpaper; + +import processing.core.PApplet; + +public class Sketch extends PApplet { + + float currH, currB; + float nextH, nextB; + float easing = 0.001f; + int lastChange = 0; + + public void settings() { + fullScreen(); + } + + public void setup() { + colorMode(HSB, 100); + currH = nextH = 100; + currB = nextB = 100; + } + + public void draw() { + background(currH, currB, 100); + updateCurrColor(); + if (5000 < millis() - lastChange) { + pickNextColor(); + lastChange = millis(); + } + } + + public void pickNextColor() { + nextH = random(100); + nextB = random(100); + } + + public void updateCurrColor() { + // Easing between current and next colors + currH += easing * (nextH - currH); + currB += easing * (nextB - currB); + } +} diff --git a/apps/wallpaper/src/main/res/layout/main.xml b/apps/wallpaper/src/main/res/layout/main.xml new file mode 100644 index 000000000..4f22eb184 --- /dev/null +++ b/apps/wallpaper/src/main/res/layout/main.xml @@ -0,0 +1,7 @@ + diff --git a/apps/wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/wallpaper/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/wallpaper/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/wallpaper/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/wallpaper/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..aee44e138 Binary files /dev/null and b/apps/wallpaper/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/wallpaper/src/main/res/values-w820dp/dimens.xml b/apps/wallpaper/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 000000000..a2d24bc10 --- /dev/null +++ b/apps/wallpaper/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/apps/wallpaper/src/main/res/values/dimens.xml b/apps/wallpaper/src/main/res/values/dimens.xml new file mode 100644 index 000000000..9cfe70043 --- /dev/null +++ b/apps/wallpaper/src/main/res/values/dimens.xml @@ -0,0 +1,3 @@ + + 16dp + \ No newline at end of file diff --git a/apps/wallpaper/src/main/res/values/strings.xml b/apps/wallpaper/src/main/res/values/strings.xml new file mode 100644 index 000000000..207396e9f --- /dev/null +++ b/apps/wallpaper/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + wallpaper + diff --git a/apps/wallpaper/src/main/res/xml/wallpaper.xml b/apps/wallpaper/src/main/res/xml/wallpaper.xml new file mode 100644 index 000000000..ec6db83cc --- /dev/null +++ b/apps/wallpaper/src/main/res/xml/wallpaper.xml @@ -0,0 +1,3 @@ + diff --git a/apps/watchface/build.gradle b/apps/watchface/build.gradle new file mode 100644 index 000000000..212b286f8 --- /dev/null +++ b/apps/watchface/build.gradle @@ -0,0 +1,36 @@ +plugins { + id 'com.android.application' +} + +android { + defaultConfig { + applicationId "processing.tests.watchface" + minSdkVersion 25 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + multiDexEnabled true + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + namespace 'watchface' +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + testImplementation 'junit:junit:4.13.2' + implementation project(':libs:processing-core') + implementation 'com.google.android.gms:play-services-wearable:18.0.0' + implementation 'com.google.android.support:wearable:2.9.0' + compileOnly 'com.google.android.wearable:wearable:2.9.0' +} diff --git a/apps/watchface/gradle.properties b/apps/watchface/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/apps/watchface/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/apps/watchface/src/main/AndroidManifest.xml b/apps/watchface/src/main/AndroidManifest.xml new file mode 100644 index 000000000..e6f990184 --- /dev/null +++ b/apps/watchface/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/apps/watchface/src/main/java/watchface/MainService.java b/apps/watchface/src/main/java/watchface/MainService.java new file mode 100644 index 000000000..6879f1b97 --- /dev/null +++ b/apps/watchface/src/main/java/watchface/MainService.java @@ -0,0 +1,15 @@ +package watchface; + +import processing.android.PWatchFaceCanvas; +//import processing.android.PWatchFaceGLES; +import processing.core.PApplet; + +// The service needs to extend PWatchFaceCanvas if the renderer in the sketch is P2D or P3D. +//public class MainService extends PWatchFaceGLES { +public class MainService extends PWatchFaceCanvas { + @Override + public PApplet createSketch() { + PApplet sketch = new Sketch(); + return sketch; + } +} diff --git a/apps/watchface/src/main/java/watchface/Sketch.java b/apps/watchface/src/main/java/watchface/Sketch.java new file mode 100644 index 000000000..9af8a1d39 --- /dev/null +++ b/apps/watchface/src/main/java/watchface/Sketch.java @@ -0,0 +1,25 @@ +package watchface; + +import processing.core.PApplet; + + +public class Sketch extends PApplet { + public void settings() { + fullScreen(); + } + + public void setup() { + frameRate(1); + textFont(createFont("Serif-Bold", 48 * displayDensity)); + textAlign(CENTER, CENTER); + fill(255); + } + + public void draw() { + background(0); + if (wearInteractive()) { + String str = hour() + ":" + nfs(minute(), 2) + ":" + nfs(second(), 2); + text(str, width/2, height/2); + } + } +} diff --git a/icons/icon-36.png b/apps/watchface/src/main/res/drawable-nodpi/bg.png similarity index 54% rename from icons/icon-36.png rename to apps/watchface/src/main/res/drawable-nodpi/bg.png index e053aca3e..6fb4fd15d 100644 Binary files a/icons/icon-36.png and b/apps/watchface/src/main/res/drawable-nodpi/bg.png differ diff --git a/apps/watchface/src/main/res/drawable-nodpi/preview_analog.png b/apps/watchface/src/main/res/drawable-nodpi/preview_analog.png new file mode 100644 index 000000000..605674c9a Binary files /dev/null and b/apps/watchface/src/main/res/drawable-nodpi/preview_analog.png differ diff --git a/apps/watchface/src/main/res/layout/main.xml b/apps/watchface/src/main/res/layout/main.xml new file mode 100644 index 000000000..190e3e161 --- /dev/null +++ b/apps/watchface/src/main/res/layout/main.xml @@ -0,0 +1,7 @@ + diff --git a/apps/watchface/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/watchface/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cde69bccc Binary files /dev/null and b/apps/watchface/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/watchface/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/watchface/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..c133a0cbd Binary files /dev/null and b/apps/watchface/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/watchface/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/watchface/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..bfa42f0e7 Binary files /dev/null and b/apps/watchface/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/watchface/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/watchface/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..324e72cdd Binary files /dev/null and b/apps/watchface/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/watchface/src/main/res/values/strings.xml b/apps/watchface/src/main/res/values/strings.xml new file mode 100644 index 000000000..5d0817ef2 --- /dev/null +++ b/apps/watchface/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + Watchface test + Watch face tapped + My Analog + diff --git a/apps/watchface/src/main/res/xml/watch_face.xml b/apps/watchface/src/main/res/xml/watch_face.xml new file mode 100644 index 000000000..11a664b76 --- /dev/null +++ b/apps/watchface/src/main/res/xml/watch_face.xml @@ -0,0 +1,2 @@ + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..5bb77278f --- /dev/null +++ b/build.gradle @@ -0,0 +1,55 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:8.0.2' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + // This was was added to address an issue in JCenter with some Android packages (https://stackoverflow.com/questions/50563338/could-not-find-runtime-jar-android-arch-lifecycleruntime1-0-0/50564224). + // JCenter is no longer used but keep it just in case. + maven { url "https://maven.google.com" } + + // Apparently needed by AndroidX dependencies + maven { url "https://jitpack.io" } + + // Needed to get google-vr dependencies + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + + mavenCentral() + google() + } + + // Set Java compatibility for all projects + plugins.withType(JavaPlugin).configureEach { + java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } + } + } + + // Set Android compatibility for all Android projects + plugins.withType(com.android.build.gradle.BasePlugin).configureEach { + android { + compileSdkVersion 33 + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + } + } +} + +tasks.register('clean', Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/build.xml b/build.xml deleted file mode 100644 index 18d4cd91d..000000000 --- a/build.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/.classpath b/core/.classpath deleted file mode 100644 index 03699eb51..000000000 --- a/core/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/core/.gitignore b/core/.gitignore deleted file mode 100644 index fe99505dc..000000000 --- a/core/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin - diff --git a/core/.project b/core/.project deleted file mode 100644 index 6efbbccd7..000000000 --- a/core/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - android-core - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/core/.settings/org.eclipse.jdt.core.prefs b/core/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 1e98e2b93..000000000 --- a/core/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,376 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.5 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.autoboxing=ignore -org.eclipse.jdt.core.compiler.problem.comparingIdentical=ignore -org.eclipse.jdt.core.compiler.problem.deadCode=ignore -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore -org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore -org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=error -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning -org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore -org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled -org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=disabled -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=ignore -org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore -org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore -org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning -org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore -org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning -org.eclipse.jdt.core.compiler.source=1.5 -org.eclipse.jdt.core.formatter.align_type_members_on_columns=false -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=18 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=18 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=18 -org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_assignment=0 -org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16 -org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 -org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80 -org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0 -org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=36 -org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 -org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=18 -org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=18 -org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=80 -org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16 -org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16 -org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=16 -org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_after_package=1 -org.eclipse.jdt.core.formatter.blank_lines_before_field=1 -org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 -org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 -org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 -org.eclipse.jdt.core.formatter.blank_lines_before_method=1 -org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 -org.eclipse.jdt.core.formatter.blank_lines_before_package=0 -org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 -org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 -org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line -org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=end_of_line -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false -org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false -org.eclipse.jdt.core.formatter.comment.format_block_comments=true -org.eclipse.jdt.core.formatter.comment.format_header=false -org.eclipse.jdt.core.formatter.comment.format_html=true -org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true -org.eclipse.jdt.core.formatter.comment.format_line_comments=false -org.eclipse.jdt.core.formatter.comment.format_source_code=true -org.eclipse.jdt.core.formatter.comment.indent_parameter_description=true -org.eclipse.jdt.core.formatter.comment.indent_root_tags=true -org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert -org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=insert -org.eclipse.jdt.core.formatter.comment.line_length=80 -org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true -org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true -org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false -org.eclipse.jdt.core.formatter.compact_else_if=true -org.eclipse.jdt.core.formatter.continuation_indentation=1 -org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 -org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off -org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on -org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false -org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true -org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true -org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_empty_lines=false -org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true -org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true -org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false -org.eclipse.jdt.core.formatter.indentation.size=2 -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_member=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert -org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body=insert -org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert -org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert -org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert -org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert -org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_binary_operator=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert -org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert -org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert -org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert -org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert -org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert -org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert -org.eclipse.jdt.core.formatter.join_lines_in_comments=true -org.eclipse.jdt.core.formatter.join_wrapped_lines=true -org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false -org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false -org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false -org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false -org.eclipse.jdt.core.formatter.lineSplit=80 -org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false -org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=true -org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 -org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 -org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true -org.eclipse.jdt.core.formatter.tabulation.char=space -org.eclipse.jdt.core.formatter.tabulation.size=2 -org.eclipse.jdt.core.formatter.use_on_off_tags=false -org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false -org.eclipse.jdt.core.formatter.wrap_before_binary_operator=true -org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true -org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true diff --git a/core/.settings/org.eclipse.jdt.ui.prefs b/core/.settings/org.eclipse.jdt.ui.prefs deleted file mode 100644 index 839f1e5e9..000000000 --- a/core/.settings/org.eclipse.jdt.ui.prefs +++ /dev/null @@ -1,56 +0,0 @@ -eclipse.preferences.version=1 -editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true -formatter_profile=_processing -formatter_settings_version=12 -sp_cleanup.add_default_serial_version_id=true -sp_cleanup.add_generated_serial_version_id=false -sp_cleanup.add_missing_annotations=true -sp_cleanup.add_missing_deprecated_annotations=false -sp_cleanup.add_missing_methods=false -sp_cleanup.add_missing_nls_tags=false -sp_cleanup.add_missing_override_annotations=true -sp_cleanup.add_missing_override_annotations_interface_methods=false -sp_cleanup.add_serial_version_id=false -sp_cleanup.always_use_blocks=true -sp_cleanup.always_use_parentheses_in_expressions=false -sp_cleanup.always_use_this_for_non_static_field_access=false -sp_cleanup.always_use_this_for_non_static_method_access=false -sp_cleanup.convert_to_enhanced_for_loop=false -sp_cleanup.correct_indentation=false -sp_cleanup.format_source_code=false -sp_cleanup.format_source_code_changes_only=false -sp_cleanup.make_local_variable_final=false -sp_cleanup.make_parameters_final=false -sp_cleanup.make_private_fields_final=true -sp_cleanup.make_type_abstract_if_missing_method=false -sp_cleanup.make_variable_declarations_final=false -sp_cleanup.never_use_blocks=false -sp_cleanup.never_use_parentheses_in_expressions=true -sp_cleanup.on_save_use_additional_actions=true -sp_cleanup.organize_imports=false -sp_cleanup.qualify_static_field_accesses_with_declaring_class=false -sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true -sp_cleanup.qualify_static_member_accesses_with_declaring_class=false -sp_cleanup.qualify_static_method_accesses_with_declaring_class=false -sp_cleanup.remove_private_constructors=true -sp_cleanup.remove_trailing_whitespaces=true -sp_cleanup.remove_trailing_whitespaces_all=true -sp_cleanup.remove_trailing_whitespaces_ignore_empty=false -sp_cleanup.remove_unnecessary_casts=false -sp_cleanup.remove_unnecessary_nls_tags=false -sp_cleanup.remove_unused_imports=false -sp_cleanup.remove_unused_local_variables=false -sp_cleanup.remove_unused_private_fields=true -sp_cleanup.remove_unused_private_members=false -sp_cleanup.remove_unused_private_methods=true -sp_cleanup.remove_unused_private_types=true -sp_cleanup.sort_members=false -sp_cleanup.sort_members_all=false -sp_cleanup.use_blocks=false -sp_cleanup.use_blocks_only_for_return_and_throw=false -sp_cleanup.use_parentheses_in_expressions=false -sp_cleanup.use_this_for_non_static_field_access=false -sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true -sp_cleanup.use_this_for_non_static_method_access=false -sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true diff --git a/core/build.xml b/core/build.xml deleted file mode 100644 index 84af864d5..000000000 --- a/core/build.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/core/src/processing/core/PShapeSVG.java b/core/src/processing/core/PShapeSVG.java deleted file mode 100644 index dfd5cd150..000000000 --- a/core/src/processing/core/PShapeSVG.java +++ /dev/null @@ -1,1905 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2006-11 Ben Fry and Casey Reas - Copyright (c) 2004-06 Michael Chang - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA -*/ - -package processing.core; - -import processing.data.*; - -import java.util.HashMap; - -import android.graphics.*; - - -/** - * SVG stands for Scalable Vector Graphics, a portable graphics format. It is - * a vector format so it allows for infinite resolution and relatively small - * file sizes. Most modern media software can view SVG files, including Adobe - * products, Firefox, etc. Illustrator and Inkscape can edit SVG files. - *

- * We have no intention of turning this into a full-featured SVG library. - * The goal of this project is a basic shape importer that is small enough - * to be included with applets, meaning that its download size should be - * in the neighborhood of 25-30k. Starting with release 0149, this library - * has been incorporated into the core via the loadShape() command, because - * vector shape data is just as important as the image data from loadImage(). - *

- * For more sophisticated import/export, consider the - * Batik - * library from the Apache Software Foundation. Future improvements to this - * library may focus on this properly supporting a specific subset of SVG, - * for instance the simpler SVG profiles known as - * SVG Tiny or Basic, - * although we still would not support the interactivity options. - * - *


- * - * A minimal example program using SVG: - * (assuming a working moo.svg is in your data folder) - * - *

- * PShape moo;
- *
- * void setup() {
- *   size(400, 400);
- *   moo = loadShape("moo.svg");
- * }
- * void draw() {
- *   background(255);
- *   shape(moo, mouseX, mouseY);
- * }
- * 
- * - * This code is based on the Candy library written by Michael Chang, which was - * later revised and expanded for use as a Processing core library by Ben Fry. - * Thanks to Ricard Marxer Pinon for help with better Inkscape support in 0154. - * - *


- * - * Late October 2008 revisions from ricardmp, incorporated by fry (0154) - *

    - *
  • Better style attribute handling, enabling better Inkscape support. - *
- * - * October 2008 revisions by fry (Processing 0149, pre-1.0) - *
    - *
  • Candy is no longer a separate library, and is instead part of core. - *
  • Loading now works through loadShape() - *
  • Shapes are now drawn using the new PGraphics shape() method. - *
- * - * August 2008 revisions by fry (Processing 0149) - *
    - *
  • Major changes to rework around PShape. - *
  • Now implementing more of the "transform" attribute. - *
- * - * February 2008 revisions by fry (Processing 0136) - *
    - *
  • Added support for quadratic curves in paths (Q, q, T, and t operators) - *
  • Support for reading SVG font data (though not rendering it yet) - *
- * - * Revisions for "Candy 2" November 2006 by fry - *
    - *
  • Switch to the new processing.xml library - *
  • Several bug fixes for parsing of shape data - *
  • Support for linear and radial gradients - *
  • Support for additional types of shapes - *
  • Added compound shapes (shapes with interior points) - *
  • Added methods to get shapes from an internal table - *
- * - * Revision 10/31/06 by flux - *
    - *
  • Now properly supports Processing 0118 - *
  • Fixed a bunch of things for Casey's students and general buggity. - *
  • Will now properly draw #FFFFFFFF colors (were being represented as -1) - *
  • SVGs without tags are now properly caught and loaded - *
  • Added a method customStyle() for overriding SVG colors/styles - *
  • Added a method SVGStyle() to go back to using SVG colors/styles - *
- * - * Some SVG objects and features may not yet be supported. - * Here is a partial list of non-included features - *
    - *
  • Rounded rectangles - *
  • Drop shadow objects - *
  • Typography - *
  • Layers added for Candy 2 - *
  • Patterns - *
  • Embedded images - *
- * - * For those interested, the SVG specification can be found - * here. - */ -public class PShapeSVG extends PShape { - XML element; - - /// Values between 0 and 1. - float opacity; - float strokeOpacity; - float fillOpacity; - - - Gradient strokeGradient; - Shader strokeGradientPaint; - String strokeName; // id of another object, gradients only? - - Gradient fillGradient; - Shader fillGradientPaint; - String fillName; // id of another object - - -// /** -// * Initializes a new SVG Object with the given filename. -// */ -// public PShapeSVG(PApplet parent, String filename) { -// // this will grab the root document, starting -// // the xml version and initial comments are ignored -// this(parent.loadXML(filename)); -// } - - - /** - * Initializes a new SVG Object from the given PNode. - */ - public PShapeSVG(XML svg) { - this(null, svg, true); - - if (!svg.getName().equals("svg")) { - throw new RuntimeException("root is not , it's <" + svg.getName() + ">"); - } - - // not proper parsing of the viewBox, but will cover us for cases where - // the width and height of the object is not specified - String viewBoxStr = svg.getString("viewBox"); - if (viewBoxStr != null) { - int[] viewBox = PApplet.parseInt(PApplet.splitTokens(viewBoxStr)); - width = viewBox[2]; - height = viewBox[3]; - } - - // TODO if viewbox is not same as width/height, then use it to scale - // the original objects. for now, viewbox only used when width/height - // are empty values (which by the spec means w/h of "100%" - String unitWidth = svg.getString("width"); - String unitHeight = svg.getString("height"); - if (unitWidth != null) { - width = parseUnitSize(unitWidth); - height = parseUnitSize(unitHeight); - } else { - if ((width == 0) || (height == 0)) { - //throw new RuntimeException("width/height not specified"); - PGraphics.showWarning("The width and/or height is not " + - "readable in the tag of this file."); - // For the spec, the default is 100% and 100%. For purposes - // here, insert a dummy value because this is prolly just a - // font or something for which the w/h doesn't matter. - width = 1; - height = 1; - } - } - - //root = new Group(null, svg); -// parseChildren(svg); // ? - } - - - protected PShapeSVG(PShapeSVG parent, XML properties, boolean parseKids) { - // Need to set this so that findChild() works. - // Otherwise 'parent' is null until addChild() is called later. - this.parent = parent; - - if (parent == null) { - // set values to their defaults according to the SVG spec - stroke = false; - strokeColor = 0xff000000; - strokeWeight = 1; - strokeCap = PConstants.SQUARE; // equivalent to BUTT in svg spec - strokeJoin = PConstants.MITER; - strokeGradient = null; - strokeGradientPaint = null; - strokeName = null; - - fill = true; - fillColor = 0xff000000; - fillGradient = null; - fillGradientPaint = null; - fillName = null; - - //hasTransform = false; - //transformation = null; //new float[] { 1, 0, 0, 1, 0, 0 }; - - strokeOpacity = 1; - fillOpacity = 1; - opacity = 1; - - } else { - stroke = parent.stroke; - strokeColor = parent.strokeColor; - strokeWeight = parent.strokeWeight; - strokeCap = parent.strokeCap; - strokeJoin = parent.strokeJoin; - strokeGradient = parent.strokeGradient; - strokeGradientPaint = parent.strokeGradientPaint; - strokeName = parent.strokeName; - - fill = parent.fill; - fillColor = parent.fillColor; - fillGradient = parent.fillGradient; - fillGradientPaint = parent.fillGradientPaint; - fillName = parent.fillName; - - //hasTransform = parent.hasTransform; - //transformation = parent.transformation; - - opacity = parent.opacity; - } - - element = properties; - name = properties.getString("id"); - // @#$(* adobe illustrator mangles names of objects when re-saving - if (name != null) { - while (true) { - String[] m = PApplet.match(name, "_x([A-Za-z0-9]{2})_"); - if (m == null) break; - char repair = (char) PApplet.unhex(m[1]); - name = name.replace(m[0], "" + repair); - } - } - - String displayStr = properties.getString("display", "inline"); - visible = !displayStr.equals("none"); - - String transformStr = properties.getString("transform"); - if (transformStr != null) { - matrix = parseTransform(transformStr); - } - - if (parseKids) { - parseColors(properties); - parseChildren(properties); - } - } - - - protected void parseChildren(XML graphics) { - XML[] elements = graphics.getChildren(); - children = new PShape[elements.length]; - childCount = 0; - - for (XML elem : elements) { - PShape kid = parseChild(elem); - if (kid != null) { -// if (kid.name != null) { -// System.out.println("adding child " + kid.name); -// } - addChild(kid); - } - } - children = (PShape[]) PApplet.subset(children, 0, childCount); - } - - - /** - * Parse a child XML element. - * Override this method to add parsing for more SVG elements. - */ - protected PShape parseChild(XML elem) { -// System.err.println("parsing child in pshape " + elem.getName()); - String name = elem.getName(); - PShapeSVG shape = null; - - if (name == null) { - // just some whitespace that can be ignored (hopefully) - - } else if (name.equals("g")) { - //return new BaseObject(this, elem); - shape = new PShapeSVG(this, elem, true); - - } else if (name.equals("defs")) { - // generally this will contain gradient info, so may - // as well just throw it into a group element for parsing - //return new BaseObject(this, elem); - shape = new PShapeSVG(this, elem, true); - - } else if (name.equals("line")) { - //return new Line(this, elem); - //return new BaseObject(this, elem, LINE); - shape = new PShapeSVG(this, elem, true); - shape.parseLine(); - - } else if (name.equals("circle")) { - //return new BaseObject(this, elem, ELLIPSE); - shape = new PShapeSVG(this, elem, true); - shape.parseEllipse(true); - - } else if (name.equals("ellipse")) { - //return new BaseObject(this, elem, ELLIPSE); - shape = new PShapeSVG(this, elem, true); - shape.parseEllipse(false); - - } else if (name.equals("rect")) { - //return new BaseObject(this, elem, RECT); - shape = new PShapeSVG(this, elem, true); - shape.parseRect(); - - } else if (name.equals("polygon")) { - //return new BaseObject(this, elem, POLYGON); - shape = new PShapeSVG(this, elem, true); - shape.parsePoly(true); - - } else if (name.equals("polyline")) { - //return new BaseObject(this, elem, POLYGON); - shape = new PShapeSVG(this, elem, true); - shape.parsePoly(false); - - } else if (name.equals("path")) { - //return new BaseObject(this, elem, PATH); - shape = new PShapeSVG(this, elem, true); - shape.parsePath(); - - } else if (name.equals("radialGradient")) { - return new RadialGradient(this, elem); - - } else if (name.equals("linearGradient")) { - return new LinearGradient(this, elem); - - } else if (name.equals("font")) { - return new Font(this, elem); - -// } else if (name.equals("font-face")) { -// return new FontFace(this, elem); - -// } else if (name.equals("glyph") || name.equals("missing-glyph")) { -// return new FontGlyph(this, elem); - - } else if (name.equals("metadata")) { - // fontforge just stuffs this in as a comment - return null; - - } else if (name.equals("text")) { // || name.equals("font")) { - PGraphics.showWarning("Text and fonts in SVG files " + - "are not currently supported, " + - "convert text to outlines instead."); - - } else if (name.equals("filter")) { - PGraphics.showWarning("Filters are not supported."); - - } else if (name.equals("mask")) { - PGraphics.showWarning("Masks are not supported."); - - } else if (name.equals("pattern")) { - PGraphics.showWarning("Patterns are not supported."); - - } else if (name.equals("stop")) { - // stop tag is handled by gradient parser, so don't warn about it - - } else if (name.equals("sodipodi:namedview")) { - // these are always in Inkscape files, the warnings get tedious - - } else { - PGraphics.showWarning("Ignoring <" + name + "> tag."); -// new Exception().printStackTrace(); - } - return shape; - } - - - protected void parseLine() { - kind = LINE; - family = PRIMITIVE; - params = new float[] { - getFloatWithUnit(element, "x1"), - getFloatWithUnit(element, "y1"), - getFloatWithUnit(element, "x2"), - getFloatWithUnit(element, "y2") - }; - } - - - /** - * Handles parsing ellipse and circle tags. - * @param circle true if this is a circle and not an ellipse - */ - protected void parseEllipse(boolean circle) { - kind = ELLIPSE; - family = PRIMITIVE; - params = new float[4]; - - params[0] = getFloatWithUnit(element, "cx"); - params[1] = getFloatWithUnit(element, "cy"); - - float rx, ry; - if (circle) { - rx = ry = getFloatWithUnit(element, "r"); - } else { - rx = getFloatWithUnit(element, "rx"); - ry = getFloatWithUnit(element, "ry"); - } - params[0] -= rx; - params[1] -= ry; - - params[2] = rx*2; - params[3] = ry*2; - } - - - protected void parseRect() { - kind = RECT; - family = PRIMITIVE; - params = new float[] { - getFloatWithUnit(element, "x"), - getFloatWithUnit(element, "y"), - getFloatWithUnit(element, "width"), - getFloatWithUnit(element, "height") - }; - } - - - /** - * Parse a polyline or polygon from an SVG file. - * @param close true if shape is closed (polygon), false if not (polyline) - */ - protected void parsePoly(boolean close) { - family = PATH; - this.close = close; - - String pointsAttr = element.getString("points"); - if (pointsAttr != null) { - String[] pointsBuffer = PApplet.splitTokens(pointsAttr); - vertexCount = pointsBuffer.length; - vertices = new float[vertexCount][2]; - for (int i = 0; i < vertexCount; i++) { - String pb[] = PApplet.split(pointsBuffer[i], ','); - vertices[i][X] = Float.valueOf(pb[0]).floatValue(); - vertices[i][Y] = Float.valueOf(pb[1]).floatValue(); - } - } - } - - - protected void parsePath() { - family = PATH; - kind = 0; - - String pathData = element.getString("d"); - if (pathData == null || PApplet.trim(pathData).length() == 0) { - return; - } - char[] pathDataChars = pathData.toCharArray(); - - StringBuffer pathBuffer = new StringBuffer(); - boolean lastSeparate = false; - - for (int i = 0; i < pathDataChars.length; i++) { - char c = pathDataChars[i]; - boolean separate = false; - - if (c == 'M' || c == 'm' || - c == 'L' || c == 'l' || - c == 'H' || c == 'h' || - c == 'V' || c == 'v' || - c == 'C' || c == 'c' || // beziers - c == 'S' || c == 's' || - c == 'Q' || c == 'q' || // quadratic beziers - c == 'T' || c == 't' || -// c == 'A' || c == 'a' || // elliptical arc - c == 'Z' || c == 'z' || // closepath - c == ',') { - separate = true; - if (i != 0) { - pathBuffer.append("|"); - } - } - if (c == 'Z' || c == 'z') { - separate = false; - } - if (c == '-' && !lastSeparate) { - // allow for 'e' notation in numbers, e.g. 2.10e-9 - // http://dev.processing.org/bugs/show_bug.cgi?id=1408 - if (i == 0 || pathDataChars[i-1] != 'e') { - pathBuffer.append("|"); - } - } - if (c != ',') { - pathBuffer.append(c); //"" + pathDataBuffer.charAt(i)); - } - if (separate && c != ',' && c != '-') { - pathBuffer.append("|"); - } - lastSeparate = separate; - } - - // use whitespace constant to get rid of extra spaces and CR or LF - String[] pathTokens = - PApplet.splitTokens(pathBuffer.toString(), "|" + WHITESPACE); - vertices = new float[pathTokens.length][2]; - vertexCodes = new int[pathTokens.length]; - - float cx = 0; - float cy = 0; - int i = 0; - - char implicitCommand = '\0'; -// char prevCommand = '\0'; - boolean prevCurve = false; - float ctrlX, ctrlY; - // store values for closepath so that relative coords work properly - float movetoX = 0; - float movetoY = 0; - - while (i < pathTokens.length) { - char c = pathTokens[i].charAt(0); - if (((c >= '0' && c <= '9') || (c == '-')) && implicitCommand != '\0') { - c = implicitCommand; - i--; - } else { - implicitCommand = c; - } - switch (c) { - - case 'M': // M - move to (absolute) - cx = PApplet.parseFloat(pathTokens[i + 1]); - cy = PApplet.parseFloat(pathTokens[i + 2]); - movetoX = cx; - movetoY = cy; - parsePathMoveto(cx, cy); - implicitCommand = 'L'; - i += 3; - break; - - case 'm': // m - move to (relative) - cx = cx + PApplet.parseFloat(pathTokens[i + 1]); - cy = cy + PApplet.parseFloat(pathTokens[i + 2]); - parsePathMoveto(cx, cy); - implicitCommand = 'l'; - i += 3; - break; - - case 'L': - cx = PApplet.parseFloat(pathTokens[i + 1]); - cy = PApplet.parseFloat(pathTokens[i + 2]); - parsePathLineto(cx, cy); - i += 3; - break; - - case 'l': - cx = cx + PApplet.parseFloat(pathTokens[i + 1]); - cy = cy + PApplet.parseFloat(pathTokens[i + 2]); - parsePathLineto(cx, cy); - i += 3; - break; - - // horizontal lineto absolute - case 'H': - cx = PApplet.parseFloat(pathTokens[i + 1]); - parsePathLineto(cx, cy); - i += 2; - break; - - // horizontal lineto relative - case 'h': - cx = cx + PApplet.parseFloat(pathTokens[i + 1]); - parsePathLineto(cx, cy); - i += 2; - break; - - case 'V': - cy = PApplet.parseFloat(pathTokens[i + 1]); - parsePathLineto(cx, cy); - i += 2; - break; - - case 'v': - cy = cy + PApplet.parseFloat(pathTokens[i + 1]); - parsePathLineto(cx, cy); - i += 2; - break; - - // C - curve to (absolute) - case 'C': { - float ctrlX1 = PApplet.parseFloat(pathTokens[i + 1]); - float ctrlY1 = PApplet.parseFloat(pathTokens[i + 2]); - float ctrlX2 = PApplet.parseFloat(pathTokens[i + 3]); - float ctrlY2 = PApplet.parseFloat(pathTokens[i + 4]); - float endX = PApplet.parseFloat(pathTokens[i + 5]); - float endY = PApplet.parseFloat(pathTokens[i + 6]); - parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); - cx = endX; - cy = endY; - i += 7; - prevCurve = true; - } - break; - - // c - curve to (relative) - case 'c': { - float ctrlX1 = cx + PApplet.parseFloat(pathTokens[i + 1]); - float ctrlY1 = cy + PApplet.parseFloat(pathTokens[i + 2]); - float ctrlX2 = cx + PApplet.parseFloat(pathTokens[i + 3]); - float ctrlY2 = cy + PApplet.parseFloat(pathTokens[i + 4]); - float endX = cx + PApplet.parseFloat(pathTokens[i + 5]); - float endY = cy + PApplet.parseFloat(pathTokens[i + 6]); - parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); - cx = endX; - cy = endY; - i += 7; - prevCurve = true; - } - break; - - // S - curve to shorthand (absolute) - // Draws a cubic Bézier curve from the current point to (x,y). The first - // control point is assumed to be the reflection of the second control - // point on the previous command relative to the current point. - // (x2,y2) is the second control point (i.e., the control point - // at the end of the curve). S (uppercase) indicates that absolute - // coordinates will follow; s (lowercase) indicates that relative - // coordinates will follow. Multiple sets of coordinates may be specified - // to draw a polybézier. At the end of the command, the new current point - // becomes the final (x,y) coordinate pair used in the polybézier. - case 'S': { - // (If there is no previous command or if the previous command was not - // an C, c, S or s, assume the first control point is coincident with - // the current point.) - if (!prevCurve) { - ctrlX = cx; - ctrlY = cy; - } else { - float ppx = vertices[vertexCount-2][X]; - float ppy = vertices[vertexCount-2][Y]; - float px = vertices[vertexCount-1][X]; - float py = vertices[vertexCount-1][Y]; - ctrlX = px + (px - ppx); - ctrlY = py + (py - ppy); - } - float ctrlX2 = PApplet.parseFloat(pathTokens[i + 1]); - float ctrlY2 = PApplet.parseFloat(pathTokens[i + 2]); - float endX = PApplet.parseFloat(pathTokens[i + 3]); - float endY = PApplet.parseFloat(pathTokens[i + 4]); - parsePathCurveto(ctrlX, ctrlY, ctrlX2, ctrlY2, endX, endY); - cx = endX; - cy = endY; - i += 5; - prevCurve = true; - } - break; - - // s - curve to shorthand (relative) - case 's': { - if (!prevCurve) { - ctrlX = cx; - ctrlY = cy; - } else { - float ppx = vertices[vertexCount-2][X]; - float ppy = vertices[vertexCount-2][Y]; - float px = vertices[vertexCount-1][X]; - float py = vertices[vertexCount-1][Y]; - ctrlX = px + (px - ppx); - ctrlY = py + (py - ppy); - } - float ctrlX2 = cx + PApplet.parseFloat(pathTokens[i + 1]); - float ctrlY2 = cy + PApplet.parseFloat(pathTokens[i + 2]); - float endX = cx + PApplet.parseFloat(pathTokens[i + 3]); - float endY = cy + PApplet.parseFloat(pathTokens[i + 4]); - parsePathCurveto(ctrlX, ctrlY, ctrlX2, ctrlY2, endX, endY); - cx = endX; - cy = endY; - i += 5; - prevCurve = true; - } - break; - - // Q - quadratic curve to (absolute) - // Draws a quadratic Bézier curve from the current point to (x,y) using - // (x1,y1) as the control point. Q (uppercase) indicates that absolute - // coordinates will follow; q (lowercase) indicates that relative - // coordinates will follow. Multiple sets of coordinates may be specified - // to draw a polybézier. At the end of the command, the new current point - // becomes the final (x,y) coordinate pair used in the polybézier. - case 'Q': { - ctrlX = PApplet.parseFloat(pathTokens[i + 1]); - ctrlY = PApplet.parseFloat(pathTokens[i + 2]); - float endX = PApplet.parseFloat(pathTokens[i + 3]); - float endY = PApplet.parseFloat(pathTokens[i + 4]); - //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); - parsePathQuadto(ctrlX, ctrlY, endX, endY); - cx = endX; - cy = endY; - i += 5; - prevCurve = true; - } - break; - - // q - quadratic curve to (relative) - case 'q': { - ctrlX = cx + PApplet.parseFloat(pathTokens[i + 1]); - ctrlY = cy + PApplet.parseFloat(pathTokens[i + 2]); - float endX = cx + PApplet.parseFloat(pathTokens[i + 3]); - float endY = cy + PApplet.parseFloat(pathTokens[i + 4]); - //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); - parsePathQuadto(ctrlX, ctrlY, endX, endY); - cx = endX; - cy = endY; - i += 5; - prevCurve = true; - } - break; - - // T - quadratic curveto shorthand (absolute) - // The control point is assumed to be the reflection of the control - // point on the previous command relative to the current point. - case 'T': { - // If there is no previous command or if the previous command was - // not a Q, q, T or t, assume the control point is coincident - // with the current point. - if (!prevCurve) { - ctrlX = cx; - ctrlY = cy; - } else { - float ppx = vertices[vertexCount-2][X]; - float ppy = vertices[vertexCount-2][Y]; - float px = vertices[vertexCount-1][X]; - float py = vertices[vertexCount-1][Y]; - ctrlX = px + (px - ppx); - ctrlY = py + (py - ppy); - } - float endX = PApplet.parseFloat(pathTokens[i + 1]); - float endY = PApplet.parseFloat(pathTokens[i + 2]); - //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); - parsePathQuadto(ctrlX, ctrlY, endX, endY); - cx = endX; - cy = endY; - i += 3; - prevCurve = true; - } - break; - - // t - quadratic curveto shorthand (relative) - case 't': { - if (!prevCurve) { - ctrlX = cx; - ctrlY = cy; - } else { - float ppx = vertices[vertexCount-2][X]; - float ppy = vertices[vertexCount-2][Y]; - float px = vertices[vertexCount-1][X]; - float py = vertices[vertexCount-1][Y]; - ctrlX = px + (px - ppx); - ctrlY = py + (py - ppy); - } - float endX = cx + PApplet.parseFloat(pathTokens[i + 1]); - float endY = cy + PApplet.parseFloat(pathTokens[i + 2]); - //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); - parsePathQuadto(ctrlX, ctrlY, endX, endY); - cx = endX; - cy = endY; - i += 3; - prevCurve = true; - } - break; - - case 'Z': - case 'z': - // since closing the path, the 'current' point needs - // to return back to the last moveto location. - // http://code.google.com/p/processing/issues/detail?id=1058 - cx = movetoX; - cy = movetoY; - close = true; - i++; - break; - - default: - String parsed = - PApplet.join(PApplet.subset(pathTokens, 0, i), ","); - String unparsed = - PApplet.join(PApplet.subset(pathTokens, i), ","); - System.err.println("parsed: " + parsed); - System.err.println("unparsed: " + unparsed); - if (pathTokens[i].equals("a") || pathTokens[i].equals("A")) { - String msg = "Sorry, elliptical arc support for SVG files " + - "is not yet implemented (See issue 130 for updates)"; - throw new RuntimeException(msg); - } - throw new RuntimeException("shape command not handled: " + pathTokens[i]); - } -// prevCommand = c; - } - } - - -// private void parsePathCheck(int num) { -// if (vertexCount + num-1 >= vertices.length) { -// //vertices = (float[][]) PApplet.expand(vertices); -// float[][] temp = new float[vertexCount << 1][2]; -// System.arraycopy(vertices, 0, temp, 0, vertexCount); -// vertices = temp; -// } -// } - - private void parsePathVertex(float x, float y) { - if (vertexCount == vertices.length) { - //vertices = (float[][]) PApplet.expand(vertices); - float[][] temp = new float[vertexCount << 1][2]; - System.arraycopy(vertices, 0, temp, 0, vertexCount); - vertices = temp; - } - vertices[vertexCount][X] = x; - vertices[vertexCount][Y] = y; - vertexCount++; - } - - - private void parsePathCode(int what) { - if (vertexCodeCount == vertexCodes.length) { - vertexCodes = PApplet.expand(vertexCodes); - } - vertexCodes[vertexCodeCount++] = what; - } - - - private void parsePathMoveto(float px, float py) { - if (vertexCount > 0) { - parsePathCode(BREAK); - } - parsePathCode(VERTEX); - parsePathVertex(px, py); - } - - - private void parsePathLineto(float px, float py) { - parsePathCode(VERTEX); - parsePathVertex(px, py); - } - - - private void parsePathCurveto(float x1, float y1, - float x2, float y2, - float x3, float y3) { - parsePathCode(BEZIER_VERTEX); - parsePathVertex(x1, y1); - parsePathVertex(x2, y2); - parsePathVertex(x3, y3); - } - -// private void parsePathQuadto(float x1, float y1, -// float cx, float cy, -// float x2, float y2) { -// //System.out.println("quadto: " + x1 + "," + y1 + " " + cx + "," + cy + " " + x2 + "," + y2); -//// parsePathCode(BEZIER_VERTEX); -// parsePathCode(QUAD_BEZIER_VERTEX); -// // x1/y1 already covered by last moveto, lineto, or curveto -// -// parsePathVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f)); -// parsePathVertex(x2 + ((cx-x2)*2/3.0f), y2 + ((cy-y2)*2/3.0f)); -// parsePathVertex(x2, y2); -// } - - private void parsePathQuadto(float cx, float cy, - float x2, float y2) { - //System.out.println("quadto: " + x1 + "," + y1 + " " + cx + "," + cy + " " + x2 + "," + y2); -// parsePathCode(BEZIER_VERTEX); - parsePathCode(QUADRATIC_VERTEX); - // x1/y1 already covered by last moveto, lineto, or curveto - parsePathVertex(cx, cy); - parsePathVertex(x2, y2); - } - - - /** - * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D - * is rotated relative to the SVG definition, so parameters are rearranged - * here. More about the transformation matrices in - * this section - * of the SVG documentation. - * @param matrixStr text of the matrix param. - * @return a good old-fashioned PMatrix2D - */ - static protected PMatrix2D parseTransform(String matrixStr) { - matrixStr = matrixStr.trim(); - PMatrix2D outgoing = null; - int start = 0; - int stop = -1; - while ((stop = matrixStr.indexOf(')', start)) != -1) { - PMatrix2D m = parseSingleTransform(matrixStr.substring(start, stop+1)); - if (outgoing == null) { - outgoing = m; - } else { - outgoing.apply(m); - } - start = stop + 1; - } - return outgoing; - } - - - static protected PMatrix2D parseSingleTransform(String matrixStr) { - //String[] pieces = PApplet.match(matrixStr, "^\\s*(\\w+)\\((.*)\\)\\s*$"); - String[] pieces = PApplet.match(matrixStr, "[,\\s]*(\\w+)\\((.*)\\)"); - if (pieces == null) { - System.err.println("Could not parse transform " + matrixStr); - return null; - } - float[] m = PApplet.parseFloat(PApplet.splitTokens(pieces[2], ", ")); - if (pieces[1].equals("matrix")) { - return new PMatrix2D(m[0], m[2], m[4], m[1], m[3], m[5]); - - } else if (pieces[1].equals("translate")) { - float tx = m[0]; - float ty = (m.length == 2) ? m[1] : m[0]; - //return new float[] { 1, 0, tx, 0, 1, ty }; - return new PMatrix2D(1, 0, tx, 0, 1, ty); - - } else if (pieces[1].equals("scale")) { - float sx = m[0]; - float sy = (m.length == 2) ? m[1] : m[0]; - //return new float[] { sx, 0, 0, 0, sy, 0 }; - return new PMatrix2D(sx, 0, 0, 0, sy, 0); - - } else if (pieces[1].equals("rotate")) { - float angle = m[0]; - - if (m.length == 1) { - float c = PApplet.cos(angle); - float s = PApplet.sin(angle); - // SVG version is cos(a) sin(a) -sin(a) cos(a) 0 0 - return new PMatrix2D(c, -s, 0, s, c, 0); - - } else if (m.length == 3) { - PMatrix2D mat = new PMatrix2D(0, 1, m[1], 1, 0, m[2]); - mat.rotate(m[0]); - mat.translate(-m[1], -m[2]); - return mat; //.get(null); - } - - } else if (pieces[1].equals("skewX")) { - return new PMatrix2D(1, 0, 1, PApplet.tan(m[0]), 0, 0); - - } else if (pieces[1].equals("skewY")) { - return new PMatrix2D(1, 0, 1, 0, PApplet.tan(m[0]), 0); - } - return null; - } - - - protected void parseColors(XML properties) { - if (properties.hasAttribute("opacity")) { - String opacityText = properties.getString("opacity"); - setOpacity(opacityText); - } - - if (properties.hasAttribute("stroke")) { - String strokeText = properties.getString("stroke"); - setColor(strokeText, false); - } - - if (properties.hasAttribute("stroke-opacity")) { - String strokeOpacityText = properties.getString("stroke-opacity"); - setStrokeOpacity(strokeOpacityText); - } - - if (properties.hasAttribute("stroke-width")) { - // if NaN (i.e. if it's 'inherit') then default back to the inherit setting - String lineweight = properties.getString("stroke-width"); - setStrokeWeight(lineweight); - } - - if (properties.hasAttribute("stroke-linejoin")) { - String linejoin = properties.getString("stroke-linejoin"); - setStrokeJoin(linejoin); - } - - if (properties.hasAttribute("stroke-linecap")) { - String linecap = properties.getString("stroke-linecap"); - setStrokeCap(linecap); - } - - // fill defaults to black (though stroke defaults to "none") - // http://www.w3.org/TR/SVG/painting.html#FillProperties - if (properties.hasAttribute("fill")) { - String fillText = properties.getString("fill"); - setColor(fillText, true); - } - - if (properties.hasAttribute("fill-opacity")) { - String fillOpacityText = properties.getString("fill-opacity"); - setFillOpacity(fillOpacityText); - } - - if (properties.hasAttribute("style")) { - String styleText = properties.getString("style"); - String[] styleTokens = PApplet.splitTokens(styleText, ";"); - - //PApplet.println(styleTokens); - for (int i = 0; i < styleTokens.length; i++) { - String[] tokens = PApplet.splitTokens(styleTokens[i], ":"); - //PApplet.println(tokens); - - tokens[0] = PApplet.trim(tokens[0]); - - if (tokens[0].equals("fill")) { - setColor(tokens[1], true); - - } else if(tokens[0].equals("fill-opacity")) { - setFillOpacity(tokens[1]); - - } else if(tokens[0].equals("stroke")) { - setColor(tokens[1], false); - - } else if(tokens[0].equals("stroke-width")) { - setStrokeWeight(tokens[1]); - - } else if(tokens[0].equals("stroke-linecap")) { - setStrokeCap(tokens[1]); - - } else if(tokens[0].equals("stroke-linejoin")) { - setStrokeJoin(tokens[1]); - - } else if(tokens[0].equals("stroke-opacity")) { - setStrokeOpacity(tokens[1]); - - } else if(tokens[0].equals("opacity")) { - setOpacity(tokens[1]); - - } else { - // Other attributes are not yet implemented - } - } - } - } - - - void setOpacity(String opacityText) { - opacity = PApplet.parseFloat(opacityText); - strokeColor = ((int) (opacity * 255)) << 24 | strokeColor & 0xFFFFFF; - fillColor = ((int) (opacity * 255)) << 24 | fillColor & 0xFFFFFF; - } - - - void setStrokeWeight(String lineweight) { - strokeWeight = parseUnitSize(lineweight); - } - - - void setStrokeOpacity(String opacityText) { - strokeOpacity = PApplet.parseFloat(opacityText); - strokeColor = ((int) (strokeOpacity * 255)) << 24 | strokeColor & 0xFFFFFF; - } - - - void setStrokeJoin(String linejoin) { - if (linejoin.equals("inherit")) { - // do nothing, will inherit automatically - - } else if (linejoin.equals("miter")) { - strokeJoin = PConstants.MITER; - - } else if (linejoin.equals("round")) { - strokeJoin = PConstants.ROUND; - - } else if (linejoin.equals("bevel")) { - strokeJoin = PConstants.BEVEL; - } - } - - - void setStrokeCap(String linecap) { - if (linecap.equals("inherit")) { - // do nothing, will inherit automatically - - } else if (linecap.equals("butt")) { - strokeCap = PConstants.SQUARE; - - } else if (linecap.equals("round")) { - strokeCap = PConstants.ROUND; - - } else if (linecap.equals("square")) { - strokeCap = PConstants.PROJECT; - } - } - - - void setFillOpacity(String opacityText) { - fillOpacity = PApplet.parseFloat(opacityText); - fillColor = ((int) (fillOpacity * 255)) << 24 | fillColor & 0xFFFFFF; - } - - - void setColor(String colorText, boolean isFill) { - int opacityMask = fillColor & 0xFF000000; - boolean visible = true; - int color = 0; - String name = ""; - Gradient gradient = null; - Shader paint = null; - if (colorText.equals("none")) { - visible = false; - } else if (colorText.equals("black")) { - color = opacityMask; - } else if (colorText.equals("white")) { - color = opacityMask | 0xFFFFFF; - } else if (colorText.startsWith("#")) { - if (colorText.length() == 4) { - // Short form: #ABC, transform to long form #AABBCC - colorText = colorText.replaceAll("^#(.)(.)(.)$", "#$1$1$2$2$3$3"); - } - color = opacityMask | - (Integer.parseInt(colorText.substring(1), 16)) & 0xFFFFFF; - //System.out.println("hex for fill is " + PApplet.hex(fillColor)); - } else if (colorText.startsWith("rgb")) { - color = opacityMask | parseRGB(colorText); - } else if (colorText.startsWith("url(#")) { - name = colorText.substring(5, colorText.length() - 1); -// PApplet.println("looking for " + name); - Object object = findChild(name); - //PApplet.println("found " + fillObject); - if (object instanceof Gradient) { - gradient = (Gradient) object; - paint = calcGradientPaint(gradient); //, opacity); - //PApplet.println("got filla " + fillObject); - } else { -// visible = false; - System.err.println("url " + name + " refers to unexpected data: " + object); - } - } - if (isFill) { - fill = visible; - fillColor = color; - fillName = name; - fillGradient = gradient; - fillGradientPaint = paint; - } else { - stroke = visible; - strokeColor = color; - strokeName = name; - strokeGradient = gradient; - strokeGradientPaint = paint; - } - } - - - static protected int parseRGB(String what) { - int leftParen = what.indexOf('(') + 1; - int rightParen = what.indexOf(')'); - String sub = what.substring(leftParen, rightParen); - int[] values = PApplet.parseInt(PApplet.splitTokens(sub, ", ")); - return (values[0] << 16) | (values[1] << 8) | (values[2]); - } - - - static protected HashMap parseStyleAttributes(String style) { - HashMap table = new HashMap(); - String[] pieces = style.split(";"); - for (int i = 0; i < pieces.length; i++) { - String[] parts = pieces[i].split(":"); - table.put(parts[0], parts[1]); - } - return table; - } - - - /** - * Used in place of element.getFloatAttribute(a) because we can - * have a unit suffix (length or coordinate). - * @param element what to parse - * @param attribute name of the attribute to get - * @return unit-parsed version of the data - */ - static protected float getFloatWithUnit(XML element, String attribute) { - String val = element.getString(attribute); - return (val == null) ? 0 : parseUnitSize(val); - } - - - /** - * Parse a size that may have a suffix for its units. - * Ignoring cases where this could also be a percentage. - * The units spec: - *
    - *
  • "1pt" equals "1.25px" (and therefore 1.25 user units) - *
  • "1pc" equals "15px" (and therefore 15 user units) - *
  • "1mm" would be "3.543307px" (3.543307 user units) - *
  • "1cm" equals "35.43307px" (and therefore 35.43307 user units) - *
  • "1in" equals "90px" (and therefore 90 user units) - *
- */ - static protected float parseUnitSize(String text) { - int len = text.length() - 2; - - if (text.endsWith("pt")) { - return PApplet.parseFloat(text.substring(0, len)) * 1.25f; - } else if (text.endsWith("pc")) { - return PApplet.parseFloat(text.substring(0, len)) * 15; - } else if (text.endsWith("mm")) { - return PApplet.parseFloat(text.substring(0, len)) * 3.543307f; - } else if (text.endsWith("cm")) { - return PApplet.parseFloat(text.substring(0, len)) * 35.43307f; - } else if (text.endsWith("in")) { - return PApplet.parseFloat(text.substring(0, len)) * 90; - } else if (text.endsWith("px")) { - return PApplet.parseFloat(text.substring(0, len)); - } else { - return PApplet.parseFloat(text); - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - static class Gradient extends PShapeSVG { - Matrix transform; - - float[] offset; - int[] color; - int count; - - public Gradient(PShapeSVG parent, XML properties) { - super(parent, properties, true); - - XML elements[] = properties.getChildren(); - offset = new float[elements.length]; - color = new int[elements.length]; - - // - for (int i = 0; i < elements.length; i++) { - XML elem = elements[i]; - String name = elem.getName(); - if (name.equals("stop")) { - String offsetAttr = elem.getString("offset"); - float div = 1.0f; - if (offsetAttr.endsWith("%")) { - div = 100.0f; - offsetAttr = offsetAttr.substring(0, offsetAttr.length() - 1); - } - offset[count] = PApplet.parseFloat(offsetAttr) / div; - String style = elem.getString("style"); - HashMap styles = parseStyleAttributes(style); - - String colorStr = styles.get("stop-color"); - if (colorStr == null) colorStr = "#000000"; - String opacityStr = styles.get("stop-opacity"); - if (opacityStr == null) opacityStr = "1"; - int tupacity = (int) (PApplet.parseFloat(opacityStr) * 255); - color[count] = (tupacity << 24) | - Integer.parseInt(colorStr.substring(1), 16); - count++; - } - } - offset = PApplet.subset(offset, 0, count); - color = PApplet.subset(color, 0, count); - } - } - - - class LinearGradient extends Gradient { - float x1, y1, x2, y2; - - public LinearGradient(PShapeSVG parent, XML properties) { - super(parent, properties); - - this.x1 = getFloatWithUnit(properties, "x1"); - this.y1 = getFloatWithUnit(properties, "y1"); - this.x2 = getFloatWithUnit(properties, "x2"); - this.y2 = getFloatWithUnit(properties, "y2"); - - String transformStr = - properties.getString("gradientTransform"); - - if (transformStr != null) { - float t[] = parseTransform(transformStr).get(null); - //this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]); - transform = new Matrix(); - transform.setValues(new float[] { // TODO don't create temp floats - t[0], t[1], t[2], - t[3], t[4], t[5], - 0, 0, 1 - }); - -// Point2D t1 = transform.transform(new Point2D.Float(x1, y1), null); -// Point2D t2 = transform.transform(new Point2D.Float(x2, y2), null); - float[] t1 = new float[] { x1, y1 }; - float[] t2 = new float[] { x2, y2 }; - transform.mapPoints(t1); - transform.mapPoints(t2); - -// this.x1 = (float) t1.getX(); -// this.y1 = (float) t1.getY(); -// this.x2 = (float) t2.getX(); -// this.y2 = (float) t2.getY(); - x1 = t1[0]; - y1 = t1[1]; - x2 = t2[0]; - y2 = t2[1]; - } - } - } - - - class RadialGradient extends Gradient { - float cx, cy, r; - - public RadialGradient(PShapeSVG parent, XML properties) { - super(parent, properties); - - this.cx = getFloatWithUnit(properties, "cx"); - this.cy = getFloatWithUnit(properties, "cy"); - this.r = getFloatWithUnit(properties, "r"); - - String transformStr = - properties.getString("gradientTransform"); - - if (transformStr != null) { - float t[] = parseTransform(transformStr).get(null); -// this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]); - transform = new Matrix(); - transform.setValues(new float[] { // TODO don't create temp floats - t[0], t[1], t[2], - t[3], t[4], t[5], - 0, 0, 1 - }); - -// Point2D t1 = transform.transform(new Point2D.Float(cx, cy), null); -// Point2D t2 = transform.transform(new Point2D.Float(cx + r, cy), null); - float[] t1 = new float[] { cx, cy }; - float[] t2 = new float[] { cx + r, cy }; - transform.mapPoints(t1); - transform.mapPoints(t2); - -// this.cx = (float) t1.getX(); -// this.cy = (float) t1.getY(); -// this.r = (float) (t2.getX() - t1.getX()); - cx = t1[0]; - cy = t1[1]; - r = t2[0] - t1[0]; - } - } - } - - -/* - class LinearGradientPaint implements Paint { - float x1, y1, x2, y2; - float[] offset; - int[] color; - int count; - float opacity; - - public LinearGradientPaint(float x1, float y1, float x2, float y2, - float[] offset, int[] color, int count, - float opacity) { - this.x1 = x1; - this.y1 = y1; - this.x2 = x2; - this.y2 = y2; - this.offset = offset; - this.color = color; - this.count = count; - this.opacity = opacity; - } - - public PaintContext createContext(ColorModel cm, - Rectangle deviceBounds, Rectangle2D userBounds, - AffineTransform xform, RenderingHints hints) { - Point2D t1 = xform.transform(new Point2D.Float(x1, y1), null); - Point2D t2 = xform.transform(new Point2D.Float(x2, y2), null); - return new LinearGradientContext((float) t1.getX(), (float) t1.getY(), - (float) t2.getX(), (float) t2.getY()); - } - - public int getTransparency() { - return TRANSLUCENT; // why not.. rather than checking each color - } - - public class LinearGradientContext implements PaintContext { - int ACCURACY = 2; - float tx1, ty1, tx2, ty2; - - public LinearGradientContext(float tx1, float ty1, float tx2, float ty2) { - this.tx1 = tx1; - this.ty1 = ty1; - this.tx2 = tx2; - this.ty2 = ty2; - } - - public void dispose() { } - - public ColorModel getColorModel() { return ColorModel.getRGBdefault(); } - - public Raster getRaster(int x, int y, int w, int h) { - WritableRaster raster = - getColorModel().createCompatibleWritableRaster(w, h); - - int[] data = new int[w * h * 4]; - - // make normalized version of base vector - float nx = tx2 - tx1; - float ny = ty2 - ty1; - float len = (float) Math.sqrt(nx*nx + ny*ny); - if (len != 0) { - nx /= len; - ny /= len; - } - - int span = (int) PApplet.dist(tx1, ty1, tx2, ty2) * ACCURACY; - if (span <= 0) { - //System.err.println("span is too small"); - // annoying edge case where the gradient isn't legit - int index = 0; - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - data[index++] = 0; - data[index++] = 0; - data[index++] = 0; - data[index++] = 255; - } - } - - } else { - int[][] interp = new int[span][4]; - int prev = 0; - for (int i = 1; i < count; i++) { - int c0 = color[i-1]; - int c1 = color[i]; - int last = (int) (offset[i] * (span-1)); - //System.out.println("last is " + last); - for (int j = prev; j <= last; j++) { - float btwn = PApplet.norm(j, prev, last); - interp[j][0] = (int) PApplet.lerp((c0 >> 16) & 0xff, (c1 >> 16) & 0xff, btwn); - interp[j][1] = (int) PApplet.lerp((c0 >> 8) & 0xff, (c1 >> 8) & 0xff, btwn); - interp[j][2] = (int) PApplet.lerp(c0 & 0xff, c1 & 0xff, btwn); - interp[j][3] = (int) (PApplet.lerp((c0 >> 24) & 0xff, (c1 >> 24) & 0xff, btwn) * opacity); - //System.out.println(j + " " + interp[j][0] + " " + interp[j][1] + " " + interp[j][2]); - } - prev = last; - } - - int index = 0; - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - //float distance = 0; //PApplet.dist(cx, cy, x + i, y + j); - //int which = PApplet.min((int) (distance * ACCURACY), interp.length-1); - float px = (x + i) - tx1; - float py = (y + j) - ty1; - // distance up the line is the dot product of the normalized - // vector of the gradient start/stop by the point being tested - int which = (int) ((px*nx + py*ny) * ACCURACY); - if (which < 0) which = 0; - if (which > interp.length-1) which = interp.length-1; - //if (which > 138) System.out.println("grabbing " + which); - - data[index++] = interp[which][0]; - data[index++] = interp[which][1]; - data[index++] = interp[which][2]; - data[index++] = interp[which][3]; - } - } - } - raster.setPixels(0, 0, w, h, data); - - return raster; - } - } - } - */ - - - /* - class RadialGradientPaint implements Paint { - float cx, cy, radius; - float[] offset; - int[] color; - int count; - float opacity; - - public RadialGradientPaint(float cx, float cy, float radius, - float[] offset, int[] color, int count, - float opacity) { - this.cx = cx; - this.cy = cy; - this.radius = radius; - this.offset = offset; - this.color = color; - this.count = count; - this.opacity = opacity; - } - - public PaintContext createContext(ColorModel cm, - Rectangle deviceBounds, Rectangle2D userBounds, - AffineTransform xform, RenderingHints hints) { - return new RadialGradientContext(); - } - - public int getTransparency() { - return TRANSLUCENT; - } - - public class RadialGradientContext implements PaintContext { - int ACCURACY = 5; - - public void dispose() {} - - public ColorModel getColorModel() { return ColorModel.getRGBdefault(); } - - public Raster getRaster(int x, int y, int w, int h) { - WritableRaster raster = - getColorModel().createCompatibleWritableRaster(w, h); - - int span = (int) radius * ACCURACY; - int[][] interp = new int[span][4]; - int prev = 0; - for (int i = 1; i < count; i++) { - int c0 = color[i-1]; - int c1 = color[i]; - int last = (int) (offset[i] * (span - 1)); - for (int j = prev; j <= last; j++) { - float btwn = PApplet.norm(j, prev, last); - interp[j][0] = (int) PApplet.lerp((c0 >> 16) & 0xff, (c1 >> 16) & 0xff, btwn); - interp[j][1] = (int) PApplet.lerp((c0 >> 8) & 0xff, (c1 >> 8) & 0xff, btwn); - interp[j][2] = (int) PApplet.lerp(c0 & 0xff, c1 & 0xff, btwn); - interp[j][3] = (int) (PApplet.lerp((c0 >> 24) & 0xff, (c1 >> 24) & 0xff, btwn) * opacity); - } - prev = last; - } - - int[] data = new int[w * h * 4]; - int index = 0; - for (int j = 0; j < h; j++) { - for (int i = 0; i < w; i++) { - float distance = PApplet.dist(cx, cy, x + i, y + j); - int which = PApplet.min((int) (distance * ACCURACY), interp.length-1); - - data[index++] = interp[which][0]; - data[index++] = interp[which][1]; - data[index++] = interp[which][2]; - data[index++] = interp[which][3]; - } - } - raster.setPixels(0, 0, w, h, data); - - return raster; - } - } - } - */ - - - protected Shader calcGradientPaint(Gradient gradient) { - // TODO just do this with the other parsing - int[] colors = new int[gradient.count]; - int opacityMask = ((int) (opacity * 255)) << 24; - for (int i = 0; i < gradient.count; i++) { - colors[i] = opacityMask | (gradient.color[i] & 0xFFFFFF); - } - - if (gradient instanceof LinearGradient) { - LinearGradient grad = (LinearGradient) gradient; -// return new LinearGradientPaint(grad.x1, grad.y1, grad.x2, grad.y2, -// grad.offset, grad.color, grad.count, -// opacity); - return new android.graphics.LinearGradient(grad.x1, grad.y1, - grad.x2, grad.y2, - colors, grad.offset, - Shader.TileMode.CLAMP ); - - } else if (gradient instanceof RadialGradient) { - RadialGradient grad = (RadialGradient) gradient; -// return new RadialGradientPaint(grad.cx, grad.cy, grad.r, -// grad.offset, grad.color, grad.count, -// opacity); - return new android.graphics.RadialGradient(grad.cx, grad.cy, grad.r, - colors, grad.offset, - Shader.TileMode.CLAMP); - } - return null; - } - - -// protected Shader calcGradientPaint(Gradient gradient, -// float x1, float y1, float x2, float y2) { -// if (gradient instanceof LinearGradient) { -// LinearGradient grad = (LinearGradient) gradient; -// return new LinearGradientPaint(x1, y1, x2, y2, -// grad.offset, grad.color, grad.count, -// opacity); -// } -// throw new RuntimeException("Not a linear gradient."); -// } - - -// protected Shader calcGradientPaint(Gradient gradient, -// float cx, float cy, float r) { -// if (gradient instanceof RadialGradient) { -// RadialGradient grad = (RadialGradient) gradient; -// return new RadialGradientPaint(cx, cy, r, -// grad.offset, grad.color, grad.count, -// opacity); -// } -// throw new RuntimeException("Not a radial gradient."); -// } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - @Override - protected void styles(PGraphics g) { - super.styles(g); - - if (g instanceof PGraphicsAndroid2D) { - PGraphicsAndroid2D gg = (PGraphicsAndroid2D) g; - - if (strokeGradient != null) { -// gg.strokeGradient = true; -// gg.strokeGradientObject = strokeGradientPaint; - gg.strokePaint.setShader(strokeGradientPaint); - } - if (fillGradient != null) { -// p2d.fillGradient = true; -// p2d.fillGradientObject = fillGradientPaint; - gg.fillPaint.setShader(fillGradientPaint); - } - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - //public void drawImpl(PGraphics g) { - // do nothing - //} - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - public class Font extends PShapeSVG { - public FontFace face; - - public HashMap namedGlyphs; - public HashMap unicodeGlyphs; - - public int glyphCount; - public FontGlyph[] glyphs; - public FontGlyph missingGlyph; - - int horizAdvX; - - - public Font(PShapeSVG parent, XML properties) { - super(parent, properties, false); -// handle(parent, properties); - - XML[] elements = properties.getChildren(); - - horizAdvX = properties.getInt("horiz-adv-x", 0); - - namedGlyphs = new HashMap(); - unicodeGlyphs = new HashMap(); - glyphCount = 0; - glyphs = new FontGlyph[elements.length]; - - for (int i = 0; i < elements.length; i++) { - String name = elements[i].getName(); - XML elem = elements[i]; - if (name == null) { - // skip it - } else if (name.equals("glyph")) { - FontGlyph fg = new FontGlyph(this, elem, this); - if (fg.isLegit()) { - if (fg.name != null) { - namedGlyphs.put(fg.name, fg); - } - if (fg.unicode != 0) { - unicodeGlyphs.put(new Character(fg.unicode), fg); - } - } - glyphs[glyphCount++] = fg; - - } else if (name.equals("missing-glyph")) { -// System.out.println("got missing glyph inside "); - missingGlyph = new FontGlyph(this, elem, this); - } else if (name.equals("font-face")) { - face = new FontFace(this, elem); - } else { - System.err.println("Ignoring " + name + " inside "); - } - } - } - - - protected void drawShape() { - // does nothing for fonts - } - - - public void drawString(PGraphics g, String str, float x, float y, float size) { - // 1) scale by the 1.0/unitsPerEm - // 2) scale up by a font size - g.pushMatrix(); - float s = size / (float) face.unitsPerEm; - //System.out.println("scale is " + s); - // swap y coord at the same time, since fonts have y=0 at baseline - g.translate(x, y); - g.scale(s, -s); - char[] c = str.toCharArray(); - for (int i = 0; i < c.length; i++) { - // call draw on each char (pulling it w/ the unicode table) - FontGlyph fg = (FontGlyph) unicodeGlyphs.get(new Character(c[i])); - if (fg != null) { - fg.draw(g); - // add horizAdvX/unitsPerEm to the x coordinate along the way - g.translate(fg.horizAdvX, 0); - } else { - System.err.println("'" + c[i] + "' not available."); - } - } - g.popMatrix(); - } - - - public void drawChar(PGraphics g, char c, float x, float y, float size) { - g.pushMatrix(); - float s = size / (float) face.unitsPerEm; - g.translate(x, y); - g.scale(s, -s); - FontGlyph fg = (FontGlyph) unicodeGlyphs.get(new Character(c)); - if (fg != null) g.shape(fg); - g.popMatrix(); - } - - - public float textWidth(String str, float size) { - float w = 0; - char[] c = str.toCharArray(); - for (int i = 0; i < c.length; i++) { - // call draw on each char (pulling it w/ the unicode table) - FontGlyph fg = (FontGlyph) unicodeGlyphs.get(new Character(c[i])); - if (fg != null) { - w += (float) fg.horizAdvX / face.unitsPerEm; - } - } - return w * size; - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - class FontFace extends PShapeSVG { - int horizOriginX; // dflt 0 - int horizOriginY; // dflt 0 -// int horizAdvX; // no dflt? - int vertOriginX; // dflt horizAdvX/2 - int vertOriginY; // dflt ascent - int vertAdvY; // dflt 1em (unitsPerEm value) - - String fontFamily; - int fontWeight; // can also be normal or bold (also comma separated) - String fontStretch; - int unitsPerEm; // dflt 1000 - int[] panose1; // dflt "0 0 0 0 0 0 0 0 0 0" - int ascent; - int descent; - int[] bbox; // spec says comma separated, tho not w/ forge - int underlineThickness; - int underlinePosition; - //String unicodeRange; // gonna ignore for now - - - public FontFace(PShapeSVG parent, XML properties) { - super(parent, properties, true); - - unitsPerEm = properties.getInt("units-per-em", 1000); - } - - - protected void drawShape() { - // nothing to draw in the font face attribute - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - public class FontGlyph extends PShapeSVG { // extends Path - public String name; - char unicode; - int horizAdvX; - - public FontGlyph(PShapeSVG parent, XML properties, Font font) { - super(parent, properties, true); - super.parsePath(); // ?? - - name = properties.getString("glyph-name"); - String u = properties.getString("unicode"); - unicode = 0; - if (u != null) { - if (u.length() == 1) { - unicode = u.charAt(0); - //System.out.println("unicode for " + name + " is " + u); - } else { - System.err.println("unicode for " + name + - " is more than one char: " + u); - } - } - if (properties.hasAttribute("horiz-adv-x")) { - horizAdvX = properties.getInt("horiz-adv-x"); - } else { - horizAdvX = font.horizAdvX; - } - } - - - protected boolean isLegit() { // TODO need a better way to handle this... - return vertexCount != 0; - } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /** - * Get a particular element based on its SVG ID. When editing SVG by hand, - * this is the id="" tag on any SVG element. When editing from Illustrator, - * these IDs can be edited by expanding the layers palette. The names used - * in the layers palette, both for the layers or the shapes and groups - * beneath them can be used here. - *
-   * // This code grabs "Layer 3" and the shapes beneath it.
-   * PShape layer3 = svg.getChild("Layer 3");
-   * 
- */ - @Override - public PShape getChild(String name) { - PShape found = super.getChild(name); - if (found == null) { - // Otherwise try with underscores instead of spaces - // (this is how Illustrator handles spaces in the layer names). - found = super.getChild(name.replace(' ', '_')); - } - // Set bounding box based on the parent bounding box - if (found != null) { -// found.x = this.x; -// found.y = this.y; - found.width = this.width; - found.height = this.height; - } - return found; - } - - - /** - * Prints out the SVG document. Useful for parsing. - */ - public void print() { - PApplet.println(element.toString()); - } -} diff --git a/core/src/processing/event/TouchEvent.java b/core/src/processing/event/TouchEvent.java deleted file mode 100644 index 12e8ef391..000000000 --- a/core/src/processing/event/TouchEvent.java +++ /dev/null @@ -1,49 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2012 The Processing Foundation - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation, version 2.1. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA -*/ - -package processing.event; - - -// PLACEHOLDER CLASS: DO NOT USE. IT HAS NOT EVEN DECIDED WHETHER -// THIS WILL BE CALLED TOUCHEVENT ONCE IT'S FINISHED. - -/* -http://developer.android.com/guide/topics/ui/ui-events.html -http://developer.android.com/reference/android/view/MotionEvent.html -http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/TouchEventClassReference/TouchEvent/TouchEvent.html -http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIGestureRecognizer_Class/Reference/Reference.html#//apple_ref/occ/cl/UIGestureRecognizer - -Apple's high-level gesture names: -tap -pinch -rotate -swipe -pan -longpress -*/ -public class TouchEvent extends Event { - - public TouchEvent(Object nativeObject, long millis, int action, int modifiers) { - super(nativeObject, millis, action, modifiers); - this.flavor = TOUCH; - } -} diff --git a/core/src/processing/opengl/LineVert.glsl b/core/src/processing/opengl/LineVert.glsl deleted file mode 100644 index d16f4d519..000000000 --- a/core/src/processing/opengl/LineVert.glsl +++ /dev/null @@ -1,85 +0,0 @@ -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2011-13 Ben Fry and Casey Reas - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General - Public License along with this library; if not, write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - */ - -#define PROCESSING_LINE_SHADER - -uniform mat4 modelviewMatrix; -uniform mat4 projectionMatrix; - -uniform vec4 viewport; -uniform int perspective; -uniform vec3 scale; - -attribute vec4 position; -attribute vec4 color; -attribute vec4 direction; - -varying vec4 vertColor; - -vec3 clipToWindow(vec4 clip, vec4 viewport) { - vec3 post_div = clip.xyz / clip.w; - vec2 xypos = (post_div.xy + vec2(1.0, 1.0)) * 0.5 * viewport.zw; - return vec3(xypos, post_div.z * 0.5 + 0.5); -} - -vec4 windowToClipVector(vec2 window, vec4 viewport, float clip_w) { - vec2 xypos = (window / viewport.zw) * 2.0; - return vec4(xypos, 0.0, 0.0) * clip_w; -} - -void main() { - vec4 posp = modelviewMatrix * position; - - // Moving vertices slightly toward the camera - // to avoid depth-fighting with the fill triangles. - // Discussed here: - // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=252848 - posp.xyz = posp.xyz * scale; - vec4 clipp = projectionMatrix * posp; - float thickness = direction.w; - - if (thickness != 0.0) { - vec4 posq = posp + modelviewMatrix * vec4(direction.xyz, 0); - posq.xyz = posq.xyz * scale; - vec4 clipq = projectionMatrix * posq; - - vec3 window_p = clipToWindow(clipp, viewport); - vec3 window_q = clipToWindow(clipq, viewport); - vec3 tangent = window_q - window_p; - - vec2 perp = normalize(vec2(-tangent.y, tangent.x)); - vec2 offset = perp * thickness; - - if (0 < perspective) { - // Perspective correction (lines will look thiner as they move away - // from the view position). - gl_Position.xy = clipp.xy + offset.xy; - gl_Position.zw = clipp.zw; - } else { - // No perspective correction. - vec4 offsetp = windowToClipVector(offset, viewport, clipp.w); - gl_Position = clipp + offsetp; - } - } else { - gl_Position = clipp; - } - - vertColor = color; -} diff --git a/done.txt b/done.txt deleted file mode 100644 index f0e7f6a77..000000000 --- a/done.txt +++ /dev/null @@ -1,741 +0,0 @@ -0232 -X Mismatched API level inside project.properties (Android mode ver. 3) -X https://github.com/processing/processing-android/issues/73 -X ecj.jar isn't found properly -X https://github.com/processing/processing-android/issues/67 -X https://github.com/processing/processing-android/pull/76 - -0228 -X figure out how to build from Eclipse JDI so we can remove tools.jar and javac -X https://github.com/processing/processing/issues/1840 -X figure out Android build w/o javac so we can remove tools.jar and javac -X also to the p5 repo with just a JRE -X remove initRequirements from Base (no longer need JDI) -X move this into Android mode? -X https://github.com/processing/processing-android/issues/46 - -X requires deleting the app before reinstalling -X just fix this like the others -X https://github.com/processing/processing-android/issues/55 - -0217 android (released alongside 2.0b9) -X split Android mode from the rest of the project -X build core when building the mode itself -X where should core files be stored? -o maybe even straight from github -X nope, means 4 MB files for each repo update -X change build script to use ../processing instead of .. as path -X make sure the download of the mode works properly -X remove the rest from the main repo -X get things checked in -X Android mode was reporting 'no mode found' -X implement Android version of command line tools (by pif) -X http://code.google.com/p/processing/issues/detail?id=1323 -X https://github.com/processing/processing/issues/1361 -X incorporate changes to processing.data package -X several additional updates to bring things in line with the mode as well - - -0216 android (2.0b8) -X Update documentation and tools for Android SDK Tools revision 21 -X http://code.google.com/p/processing/issues/detail?id=1398 -X update Wiki to reflect no need for Google APIs -o instructions on installing the usb driver for windows -o http://developer.android.com/sdk/win-usb.html -o need to post android examples -o check out andres' changes for PShape -X look into touch event code, see if there's a good way to integrate -o make a decision on how to integrate touch event code -X punting until later -X add clear and close to all stream methods? -X http://code.google.com/p/processing/issues/detail?id=244 -X check on this, fixed one, all set -o gui stuff: http://hg.postspectacular.com/cp5magic/wiki/Home -X OpenGL sketches crashes on older Android devices -X http://code.google.com/p/processing/issues/detail?id=1455 -X remove mouseEvent and keyEvent variables (deprecated on desktop) - -earlier -X inside AndroidPreprocessor -X processing.mode.java.JavaBuild.scrubComments(sketch.getCode(0).getProgram()) -X PApplet.match(scrubbed, processing.mode.java.JavaBuild.SIZE_REGEX); -X clean up earlier when size() stuff was moved up -o test libraries on android -X Implement a way to include the resources directory of an Android app -X USB host and NFC reader need other changes to the app hierarchy to work -X http://code.google.com/p/processing/issues/detail?id=767 -X Error for "android create avd" when the AVD is already installed -X http://code.google.com/p/processing/issues/detail?id=614 - - -0215 android (2.0b7) -X removing default imports for -X android.view.MotionEvent, android.view.KeyEvent,android.graphics.Bitmap -X due to conflicts w/ the new p5 event system -X change event handling to hopefully clean up some inconsistencies -X remove motionX/Y/Pressure... these need to be handled separately -X mouseX/Y no longer include history with moves -X better to use motion object when that's done -o coordinates from motionX/Y reportedly inconsistent -X http://code.google.com/p/processing/issues/detail?id=1018 -X moving away from this anyway -X pmouseX/Y not being set properly? -X http://code.google.com/p/processing/issues/detail?id=238 -X mouseEvent is back (see the Wiki) -o add method to bring up the keyboard in sketches -o actually, just write an example of how to do it, since holding menu works -o http://code.google.com/p/processing/issues/detail?id=234 -X values for pmouseX/Y aren't great -X Examples > Topics > Drawing > Continuous Lines shows gaps -X http://code.google.com/p/processing/issues/detail?id=238 -X debug information not coming through (Windows only?) -X http://code.google.com/p/processing/issues/detail?id=1440 -X "no library found" with android.text.format (so android.* is a problem) -X Remove requirement for Google APIs in Android mode -X http://code.google.com/p/processing/issues/detail?id=613 - -cleaning/earlier -A Defects in the tessellation of SVG shapes in A3D -A http://code.google.com/p/processing/issues/detail?id=291 -X change run/present/export/export application names in the menus -A Blacked-out screen when restoring Android app from background. -A http://code.google.com/p/processing/issues/detail?id=381 -X android sdk/build location has changed (android-7 not 2.1) fix build.xml -A excessive rotation of application causes memory to run out -A this probably means that some memory isn't being freed that should be -A new window and surfaceview objects are being created in onCreate -A so they should probably be taken down in onDestroy.. but how? -A http://code.google.com/p/processing/issues/detail?id=235 -o should alpha PImage stuff use a non-4byte config? -X http://code.google.com/p/processing/issues/detail?id=242 -X hasn't emerged as a real issue -o try using the internal javac on windows and see if exceptions come through.. -o actually i think that might have been worse... -X rounded rect support -X http://code.google.com/p/processing/issues/detail?id=929 -o too many temporary objects (particularly w/ color) created with A2D -X http://code.google.com/p/processing/issues/detail?id=213 -X no votes after a couple years -X resize() needs to use the android resize stuff -o right now using the rather than expensive copy() -X instead, create a new resized bitmap, and get rid of pixels[] -X http://code.google.com/p/processing/issues/detail?id=239 - -motion events -o registerMethod("motionEvent") and "mouseEvent" not implemented -o PMotionEvent is being used internal to PApplet, need to re-wrap -o should be able to fire MotionEvents -o modify MotionEvent code to use TouchEvent class, even if basic version -o internally, PApplet might subclass it for the pointers -o but queueing needs to work -X opting to take a step backwards on the motion event handling -o write a little stub code for people who want to use motionX/Y/etc - - -0214 android (2.0b6) -X No changes on the Android side of things - - -0213 android (2.0b5) -no changes - - -0212 android (2.0b4) -X key value is not set in android mode -X http://code.google.com/p/processing/issues/detail?id=1254 -X hue() in HSB mode returns out of range values -X hue method on Android may just be different -X http://code.google.com/p/processing/issues/detail?id=1257 - - -0211 android (2.0b3) -X implement registerMethod(keyEvent) (motion and mouse still unavailable) - - -0210 android (2.0b2) -X lots of example updates from Andres -X update example categories in the browser - - -0209 android (2.0b1) -A GL android sketch stops running after rotation -A http://code.google.com/p/processing/issues/detail?id=1146 -X lack of registerXxx() implementation is breaking pre() and post() in particles -X pause/resume trickiness with interactive apps -X can we serialize objects (even if slow?) -X add pause() and resume() methods to PApplet -> this is start/stop -X register(this, "pause") -> libs will need pause events on android - - -0208 android (2.0a9) -X inherited PShape API changes from the desktop version - - -0207 android (2.0a8) -X lots of cleanup on this todo list -X Support Native Code in Libraries for Android (includes fix) -X http://code.google.com/p/processing/issues/detail?id=1117 -X patch from m4rlonj - -cleaning/earlier -X exceptions with StreamPump and adb devices on osx and linux -X http://code.google.com/p/processing/issues/detail?id=252 -o when starting the emulator, the adb server gets reset -o then it causes this exception, which kills the thread waiting for input -o so another reset is necessary -Exception in thread "StreamPump 49" java.lang.RuntimeException: Inside processing.app.exec.StreamPump@1ebe8ec for out: adb devices - at processing.app.exec.StreamPump.run(StreamPump.java:82) - at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) - at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) - at java.lang.Thread.run(Thread.java:637) -Caused by: java.io.IOException: Stream closed - at java.io.BufferedInputStream.getBufIfOpen(BufferedInputStream.java:145) - at java.io.BufferedInputStream.read1(BufferedInputStream.java:255) - at java.io.BufferedInputStream.read(BufferedInputStream.java:317) - at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264) - at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306) - at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158) - at java.io.InputStreamReader.read(InputStreamReader.java:167) - at java.io.BufferedReader.fill(BufferedReader.java:136) - at java.io.BufferedReader.readLine(BufferedReader.java:299) - at java.io.BufferedReader.readLine(BufferedReader.java:362) - at processing.app.exec.StreamPump.run(StreamPump.java:71) - ... 3 more -X when returning to the application after link(), screen stays blank -X http://code.google.com/p/processing/issues/detail?id=237 -X fixed in 0195 -X remove various debug messages on the console -X right now, there are too many places where errors occur -X http://code.google.com/p/processing/issues/detail?id=204 -J only send error text to System.err (i.e. "Launching emulator" is not an error) -J send these debug log messages to System.out -o "Sketch started on emulator" in spite of the emulator only halfway booted -o sent email about this one -o additional manifest gui necessary: -X icons are apparently important, android:icon -X http://developer.android.com/guide/practices/ui_guidelines/icon_design.html - - -0206 android (2.0a7) -X add full PAppletMethods implementation to Android -X swap Run on Device and Run on Emulator -X http://code.google.com/p/processing/issues/detail?id=1083 -X XML crash on loading because of desktop-specific attribute -X error: "http://apache.org/xml/features/nonvalidating/load-external-dtd" -X http://code.google.com/p/processing/issues/detail?id=1128 -X fix problems with XML loading so that PShape works again -X http://code.google.com/p/processing/issues/detail?id=1054 -X sketch names cannot start with underscore -X http://code.google.com/p/processing/issues/detail?id=1047 - - -0205 android (2.0a6) -X finish xml writing on android -X http://stackoverflow.com/questions/2290945/writing-xml-on-android -X consider switch to android 2.2 as the minimum -X screenWidth/Height replaced with displayWidth/Height -X update this on the Wiki page -X docs: P2D and P3D are now OpenGL variations -X Android mode is broken on Windows in Processing 2.0a5 -o file a bug for this w/ Google -X http://code.google.com/p/processing/issues/detail?id=1022 - -andres -A GL2 specific code in Processing 2.0a5 break P3D on GLES2 hardware -A http://code.google.com/p/processing/issues/detail?id=1029 -A OpenGL/ES requires precision specifier on float types -A http://code.google.com/p/processing/issues/detail?id=1035 -A loadshape with obj file broken in 2.05a android mode. -A http://code.google.com/p/processing/issues/detail?id=1048 -A camera() and arc() don't work together -A http://code.google.com/p/processing/issues/detail?id=751 - -earlier -X remove unnecessary processing.xml.* code from android-core -X http://code.google.com/p/processing/issues/detail?id=214 -X remove unnecessary processing.xml.* code from android-core -X http://code.google.com/p/processing/issues/detail?id=214 - - -0204 android (2.0a5) -X Android emulator not launching on Windows with 2.0 alpha releases -X http://code.google.com/p/processing/issues/detail?id=899 -X http://code.google.com/p/processing/issues/detail?id=769 -X /opt/android using version #s again? fix build script (earlier) -X smooth() is now the default -X update to Android tools 17 -X add workarounds for problem with tools 17 version of dex -o import statements with android.* break things -X http://code.google.com/p/processing/issues/detail?id=989 -X was already fixed -X now requiring SDK 10 (2.3.3) because of OpenGL issues -X not SDK 9, which is 2.3.1 (and still Gingerbread) -X noted on the changes page in the Wiki -X make a note of the change on the Android Wiki -X modify the .java build to require it -X make sure build.xml uses it too - - -0203 android (2.0a4) -X fix incessant "inefficient font rendering" message -X fix build.xml to point at the correct SDK version - - -0202 android (2.0a3) -X fix problem with export menu, keys, toolbar being different -X change default package name a bit -X switch to SDK 8 (Android 2.2) as the minimum -X update the project files for Android SDK Tools Revision 15 (now required) -X launching on emulator not working well -X Latest android sdk breaks processing - new build.xml output required -X http://code.google.com/p/processing/issues/detail?id=876 -X remove 'processing.test' package--people are posting on the market w/ it -X too many 'already exists' messages -X fix problem where creating a new AVD didn't update the device list -X remove 'includeantruntime' warning -X Android mode does not recognize library imports -X http://code.google.com/p/processing/issues/detail?id=766 -X "Date could not be parsed" error -X http://code.google.com/p/processing/issues/detail?id=864 - - -0201 android (2.0a2) -X lots of updates to PGraphics et al, especially on the 3D side -X change export menu/key/toolbar ordering - - -0200 android -X fix BufferedHttpEntity problem -X was trying to allocate a byte buffer for entire size of HTTP file - - -0199 android -X tries to create and AVD when running on the phone.. why? -X this is a real problem, causes lots of crashing on startup - - -0198 android -A mask() has no effect unless image has already been drawn in A3D -A http://code.google.com/p/processing/issues/detail?id=623 -A point() doesn't render in A3D -A http://code.google.com/p/processing/issues/detail?id=592 -A excessive rotation of application causes memory to run out -A http://code.google.com/p/processing/issues/detail?id=235 -A mirroring in A3D when background() not called within draw() -A http://code.google.com/p/processing/issues/detail?id=624 -X remove A2D and A3D constants -o colorMode() error -o http://code.google.com/p/processing/issues/detail?id=223 - - -0197 android (1.5.1) - -fixed earlier -X compiler errors on Windows not appearing, nor highlighting the line number -X http://code.google.com/p/processing/issues/detail?id=253 - - -0196 android (1.5) -X workaround for loadImage(url) bug in Google's Android source (via psoden) -X http://code.google.com/p/processing/issues/detail?id=629 - -earlier -X Build an interface for control of permissions on Android -X http://code.google.com/p/processing/issues/detail?id=275 -X Implement createGraphics() -X http://code.google.com/p/processing/issues/detail?id=240 -X Android 0192 sketch in static mode crashes on exit -X http://code.google.com/p/processing/issues/detail?id=518 - - -0195 android (pre) -X point() doesn't render in A3D -X http://code.google.com/p/processing/issues/detail?id=592 -X Processing 0194 + Android = "Starting Build" -X http://code.google.com/p/processing/issues/detail?id=590 -X hanging at problem where it's prompting for info on the cmd line -o renaming the old AVD is fixing a lot of issues with hanging at startup -X modes/android/android-core.zip (No such file or directory) -X problem was that the build was ignoring no sdk silently -X need to require ANDROID_SDK set for build.xml dist -X http://code.google.com/p/processing/issues/detail?id=577 -X No library found for android.content (and all the others) -X need to suppress the messages ala java.* -o ctrl-shift-r (run on device) is totally hosed -X when returning to android application, sometimes screen stays black -X http://code.google.com/p/processing/issues/detail?id=237 -X Device Killed or Disconnected Error Message with Libraries -X http://code.google.com/p/processing/issues/detail?id=565 -X there's gotta be something we can do to get better ant build message -X "Unable to resolve target 'Google...'" when APIs aren't installed -X add an error message that explains what to do -X canceling an attempt to find the Android SDK leaves no window open -X (Linux and Windows) -X crash when trying to change Android mode void sketch no Android SDK Installed -X http://code.google.com/p/processing/issues/detail?id=605 - -earlier -X Error 336 Can't run any sketch. -X javac.exe was not included with the download -X http://code.google.com/p/processing/issues/detail?id=393 - - -0194 android (pre) -X Can't change Sketch Permissions -X http://code.google.com/p/processing/issues/detail?id=559 - - -0193 android (pre) -o verify that processing-core.jar has downloaded properly -o http://code.google.com/p/processing/issues/detail?id=421 -o this is the download page: -o http://code.google.com/p/processing/downloads/detail?name=processing-android-core-0191.zip -o so look for "SHA1 Checksum: 1ed63f9316441fe46949ef5a92a28f58cc7ee226" -X just use generic android apis as requirement, not the google apis -X though it does give us the emulator skin.. maybe another option -X android debug certificate expired -X http://forum.processing.org/topic/ant-rules-r3-xml-209-395-error#25080000000262001 -X just delete ~/.android/debug.keystore -X .java files are reported with Syntax Error with the Android build 0191 -X http://code.google.com/p/processing/issues/detail?id=404 -X implement mode switching for android/java/etc -X save state re: whether sketches are android or java mode (or others?) -X http://dev.processing.org/bugs/show_bug.cgi?id=1380 -X http://code.google.com/p/processing/issues/detail?id=202 -X android mode is currently per-editor (and clunky) -X http://dev.processing.org/bugs/show_bug.cgi?id=1379 -X http://code.google.com/p/processing/issues/detail?id=201 -X remove use of clone() for images -X http://code.google.com/p/processing/issues/detail?id=42 - - -0192 android (pre) -X compile android-core with java 5 as the target so that it works on OS X 10.5 -X A3D should use lower color depth on older devices -X http://code.google.com/p/processing/issues/detail?id=391 -X new api for begin/endRecord() -A Finish opengl blending modes in A3D -A http://code.google.com/p/processing/issues/detail?id=290 -A Automatic normal calculation in A3D -A http://code.google.com/p/processing/issues/detail?id=345 -A Improve texture handling in A3D's PFont -A http://code.google.com/p/processing/issues/detail?id=394 -A OpenGL resource release mechanism in A3D is broken -A http://code.google.com/p/processing/issues/detail?id=456 -A Multitexturing in A3D -A http://code.google.com/p/processing/issues/detail?id=344 -A Problems when loading images asynchronously in A3D. -A http://code.google.com/p/processing/issues/detail?id=465 -X make several changes to get android running properly with sdk tools r8 - - -0191 android (pre) -X won't interpret size() in Android Mode without spaces between arguments -X http://code.google.com/p/processing/issues/detail?id=390 - -A Implement offscreen operations in A3D when FBO extension is not available -A http://code.google.com/p/processing/issues/detail?id=300 -A Get opengl matrices in A3D when GL_OES_matrix_get extension is not available -A http://code.google.com/p/processing/issues/detail?id=286 -A Implement calculateModelviewInverse() in A3D -A http://code.google.com/p/processing/issues/detail?id=287 -A Automatic clear/noClear() switch in A3D -A http://code.google.com/p/processing/issues/detail?id=289 -A Camera issues in A3D -A http://code.google.com/p/processing/issues/detail?id=367 -A major fixes for type to work properly in 3D (fixes KineticType) -A http://code.google.com/p/processing/issues/detail?id=358 -A Lighting and materials testing in A3D -A http://code.google.com/p/processing/issues/detail?id=294 -A Generate mipmaps when the GL_OES_generate_mipmaps extension is not available. -A http://code.google.com/p/processing/issues/detail?id=288 -A Finish screen pixels/texture operations in A3D -A http://code.google.com/p/processing/issues/detail?id=298 - -1) I fixed a bug in the camera handling that a user pointed out recently (http://forum.processing.org/topic/possible-3d-bug). This was a quite urgent issue, since affected pretty much everything. It went unnoticed until now because the math error canceled out with the default camera settings. -2) I also finished the implementation of the getImpl() method in PImage, so it initializes the texture of the new image in A3D mode. This makes the CubicVR example to work fine. - - -0190 android (pre) -X allow screenWidth/Height as parameters to size() -X right now would cause NumberFormatException -X add notes to the wiki about the size() method -X make sure sketchRenderer()/sketchWidth()/sketchHeight() are working on desktop -o see about getting them documented in the reference -X do a writeup of the size() method in the wiki -X size() command is currently ignored in Android -X http://dev.processing.org/bugs/show_bug.cgi?id=1397 -X http://code.google.com/p/processing/issues/detail?id=211 -X Implement P3D, OpenGL, A3D for Android -X http://dev.processing.org/bugs/show_bug.cgi?id=1396 -X fix mouseX/Y mapping when using smaller screen sizes -o image() problems (includes sketch) -o http://dev.processing.org/bugs/show_bug.cgi?id=1565 -o next time the avd is updated -o remove old AVDs because their APIs might be out of date -o probably need to name AVDs based on their API spec -o http://dev.processing.org/bugs/show_bug.cgi?id=1526 -o http://code.google.com/p/processing/issues/detail?id=249 -X text ascent/descent problem, text("blah\nblah") doesn't work properly -X reverting to using the PGraphics version rather than P2D -X because Paint.ascent() is returning negative values -X properly handle setting whatever permissions are necessary -X added dialog box to set permissions -X re: rewriting manifest on each build -o http://dev.processing.org/bugs/show_bug.cgi?id=1429 -X http://code.google.com/p/processing/issues/detail?id=221 -X change skewX/Y to shearX/Y -X make updated reference -X copy the changes over from the xml library -X remove 'import processing.opengl.*' in the preprocessor? -X add to wiki - rename 'data' folder to 'assets' when inside eclipse -X prevent rotation of applications? (or require a certain orientation) -o add to activity tag in the manifest: -o android:configChanges="keyboardHidden|orientation" -o android:screenOrientation="landscape" -X or programmatically specify: -X setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); -X setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); -X get the dpi of the screen (added to wiki) -X http://developer.android.com/reference/android/util/DisplayMetrics.html -X add hutch stuff to the wiki -X http://lukehutch.wordpress.com/2010/01/06/my-multi-touch-code-ported-to-eclair/ -X also for the wiki: -X add information about permissions, since loadStrings() will break -X add information to wiki about preparing apps for release -X http://developer.android.com/guide/publishing/preparing.html -X make size() work to place the component at the center of the screen -X make size() work to change the renderer -X how does size work? -X if size() method is used, things are scaled based on that -X if no size() method, then the full screen/full resolution is used -X make apps properly handle screen resize -o remove SurfaceView2D/SurfaceView3D separation, or clean up -o test controlp5 with android -X get core.zip out of svn (once tool is separate, modes stuff working etc) -X fix the width of the build window -X some sort of warning re: messing with AndroidManifest.xml -X added to the wiki -X implement createGraphics() for A3D/P3D/OPENGL -X http://dev.processing.org/bugs/show_bug.cgi?id=1488 -X http://code.google.com/p/processing/issues/detail?id=240 -X added a note to the wiki -X make sure that AndroidManifest and other files are copied on Save As -X icons.. any others? -X Errors show up that .java files are duplicates with the Android tools. -X problem is with the packages and where the preproc is putting the file -X example sketch added to bug report -X http://code.google.com/p/processing/issues/detail?id=232 -X prevent adding the opengl library when it's not needed -X preprocessor removes the code by before export -X added a note to the wiki -X implement android menu -X reset option -X permissions -X run the 'android' application (since finding its location is painful) -o remove the need for a "Reset Android" menu option -o might need to just put this in the menu for times when things go weird -o http://dev.processing.org/bugs/show_bug.cgi?id=1392 -o http://code.google.com/p/processing/issues/detail?id=209 -X added orientation(PORTRAIT) and orientation(LANDSCAPE) - -earlier -X if sketchRenderer() et al are used in android, need to add to desktop -X remove processing.opengl.* classes and finish PGraphicsAndroid3D -X http://dev.processing.org/bugs/show_bug.cgi?id=1401 -J need to prevent hitting 'run' twice (threaded, so would work) -J currently things just keep restarting the build, bad state -J http://dev.processing.org/bugs/show_bug.cgi?id=1387 - -before 0190 release -X post processing-android-core-0190.zip to the download page -X get some help w/ the dist script to make the right file -X have casey do a reference build (skewX/Y to shearX/Y) -X also changes to the xml api naming - - -0189 android (1.2.1) -X no changes - - -0188 android (1.2) -X no changes (and android features hidden) - - -0187 android (pre) -X don't kill adb server each time that run is hit -X move about.txt to the wiki - -done in 0186 -X move to Android 2.1 as the minimum requirement? -X since 2.0.1 seems to be used on only a 0.46% of devices? -X http://developer.android.com/resources/dashboard/platform-versions.html -X need to side-port changes from PShape/SVG over to Android - - -0186 android (pre) -X move to sdk 7 because 6 has been deprecated -X merged svg changes with those in desktop core - - -0185 android (pre) -X fix two bugs with fonts created with specific charsets -X fix for adobe illustrator-mangled svg id names with hex characters -X add redirect and fix problem with core.zip location after server move -X http://code.google.com/p/processing/issues/detail?id=269 -X added android.permission.INTERNET -X and android.permission.WRITE_EXTERNAL_STORAGE - - -0184 android (pre) -o when running a sketch, need to unlock the device -o http://dev.processing.org/bugs/show_bug.cgi?id=1500 -X add Pattern caching to match() to speed things up -X make saveStream() return a boolean to indicate success -X implement 'export' to create a local android folder -X 'stop' is now a no-op for the android tools -X partially fixed but hangs after pressing stop -X http://dev.processing.org/bugs/show_bug.cgi?id=1386 -X path problems finding javac.exe on vista -X http://dev.processing.org/bugs/show_bug.cgi?id=1528 - -earlier -J figure out how to set ANDROID_SDK for the build scripts -J right now, just setting the env value is required - - -0183 android (pre-release) -X mention in the notes -o emulator doesn't always start the sketch the first time -X emulator stays open because it's too slow to restart it -X add notes to about.txt, and add link to it - - -0182 android (pre-release) -X added skewX/Y implementation -X http://dev.processing.org/bugs/show_bug.cgi?id=1448 - - -0181 android (pre-release) -X some changes to A3D - - -0180 android (pre-release) -X change sdk to use revision 6 (2.0.1) as the version -X change instructions on site -X add support for a default font - - -0179 (1.1, but no android tools) -X screenWidth/Height instead of screenW/H -X errors that happen inside events (e.g. keys) not highlighting lines -X useful stack trace information not coming through.. why? -X http://dev.processing.org/bugs/show_bug.cgi?id=1384 - -previous -X don't require JDK installation and PATH setting -X if using processing with java download, should be able to use its tools.jar -X http://dev.processing.org/bugs/show_bug.cgi?id=1470 - - -0178 (private) -X noLoop() is broken (draw is never called) -X http://dev.processing.org/bugs/show_bug.cgi?id=1467 -X fix the freakout that happens with onPause() -o solution is to call stop() to kill the thread, but that's not a pause -X http://dev.processing.org/bugs/show_bug.cgi?id=1483 -X app not pausing or closing when switching to another activity -X http://dev.processing.org/bugs/show_bug.cgi?id=1404 -o if !looping, is it necessary to call redraw() in onResume()? -X doesn't appear to be, since it gets completely rebuilt -X bezier curves were broken in A2D -X extra point is drawn connecting the shape to the corner -X fix other minor bugs in shape drawing -X mask() now implemented in A2D -X updatePixels() now work properly for A2D -X set() should now be working -X using set() on an image that doesn't have a bitmap, or has pixels loaded -X requestImage() now working -X drastically improve the performance of the time functions -X point wasn't detecting different stroke weights -X point wasn't working with strokeWeight > 1 -X fix rotate() bug (was using degrees instead of radians) -X http://dev.processing.org/bugs/show_bug.cgi?id=1497 -X arc() now working properly -X createGraphics() works, at least with A2D (or aliases P2D and JAVA2D) -X "The application ... has stopped unexpectedly." when quitting a slow app -X http://dev.processing.org/bugs/show_bug.cgi?id=1485 -X Examples > Topics > Effects > Wormhole - needs work, something with resize() -X test createFont() -X createGraphics() broken -X http://dev.processing.org/bugs/show_bug.cgi?id=1437 -X remove legacy PGraphics3D class from processing.core.android -X http://dev.processing.org/bugs/show_bug.cgi?id=1402 -X is there a way to get a width/height default to use for surfaceChanged? - -cleaning -X move to eclair (donut devices will have terrible performance anyway) -X make change from src folder to assets folder for export -X what to do with other classes that rely on PApplet? (e.g. vida) -X remove drawCube() method from PApplet -X add methods to request rendering -o need to deal with fps problems with this model -o gz files not allowed, need to remove the code from createInput() et al -X or maybe not, since could still come from a site or a File? -X just can't come from the assets file.. - -jdf -X get stdout and stderr from the emulator/device -X http://dev.processing.org/bugs/show_bug.cgi?id=1381 -X remove ANDROID_SDK env variable requirement -X http://dev.processing.org/bugs/show_bug.cgi?id=1471 -X ANDROID_SDK doesn't seem to be mentioned anywhere anymore -X this may mean that p5 might just need to get the location -X ANDROID_SDK not getting set on OS X -X http://dev.processing.org/bugs/show_bug.cgi?id=1469 -X http://stackoverflow.com/questions/603785/environment-variables-in-mac-os-x -X timeout isn't long enough for emulator to boot and startup -X always times out on a 3 GHz OS X machine - - -0177 (private) -X fix error with typo in the build file: -X Open quote is expected for attribute "{1}" associated with an -X element type "android:minSdkVersion". - - -0176 (private) -X begin the merge of the new A3D, remove old OpenGL code -X escape slashes properly on windows (also place sdk in program files) -X writer.println("sdk.dir=" + Android.sdkPath); // r4 of the sdk -X writer.println("sdk.dir=" + Android.sdkPath.replace("\\", "\\" +"\\")); -X move processing.android.core back to processing.core -X need to document the rationale -X Android tools on Windows are broken due to naming changes in r4 SDK -X http://dev.processing.org/bugs/show_bug.cgi?id=1432 -X images with tint() starting to work -X tint() causes crash -X http://dev.processing.org/bugs/show_bug.cgi?id=1435 -X loadFont() now working, createFont() not tested yet -X implement library support for android -X also code folder support - - -0175 (private) -X fix problem with windows claiming "does not appear to contain an Android SDK" -X loadImage() and other loadXxxx() functions not yet working -X http://dev.processing.org/bugs/show_bug.cgi?id=1414 -X can the data folder have subfolders? - yes - - -0174 (private) -X not handling key characters correctly (space bar, tab, others) -X http://dev.processing.org/bugs/show_bug.cgi?id=1405 -X why aren't mouse drag events coming through? -X http://dev.processing.org/bugs/show_bug.cgi?id=1382 -X "taskdef class com.android.ant.SetupTask cannot be found" on Linux -X http://dev.processing.org/bugs/show_bug.cgi?id=1407 -X update to r4 of android sdk -X this release will not run on r3 or earlier -X download core.zip from local files - diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..41d9927a4 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..d6e308a63 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..f5feea6d6 --- /dev/null +++ b/gradlew @@ -0,0 +1,252 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/icons/icon-48.png b/icons/icon-48.png deleted file mode 100644 index b917df84b..000000000 Binary files a/icons/icon-48.png and /dev/null differ diff --git a/icons/icon-72.png b/icons/icon-72.png deleted file mode 100644 index d94677a69..000000000 Binary files a/icons/icon-72.png and /dev/null differ diff --git a/libs/google-vr/build.gradle b/libs/google-vr/build.gradle new file mode 100644 index 000000000..9d7c8f77b --- /dev/null +++ b/libs/google-vr/build.gradle @@ -0,0 +1,7 @@ +// Dummy Gradle project to be able to import local aar files: +// https://stackoverflow.com/a/60888941 + +configurations.maybeCreate("default") +artifacts.add("default", file('sdk-base-1.180.0.aar')) +artifacts.add("default", file('sdk-common-1.180.0.aar')) +artifacts.add("default", file('sdk-audio-1.180.0.aar')) diff --git a/libs/google-vr/sdk-audio-1.180.0.aar b/libs/google-vr/sdk-audio-1.180.0.aar new file mode 100644 index 000000000..007485cf4 Binary files /dev/null and b/libs/google-vr/sdk-audio-1.180.0.aar differ diff --git a/libs/google-vr/sdk-base-1.180.0.aar b/libs/google-vr/sdk-base-1.180.0.aar new file mode 100644 index 000000000..e9047d226 Binary files /dev/null and b/libs/google-vr/sdk-base-1.180.0.aar differ diff --git a/libs/google-vr/sdk-common-1.180.0.aar b/libs/google-vr/sdk-common-1.180.0.aar new file mode 100644 index 000000000..7ba5cff92 Binary files /dev/null and b/libs/google-vr/sdk-common-1.180.0.aar differ diff --git a/libs/processing-ar/build.gradle b/libs/processing-ar/build.gradle new file mode 100755 index 000000000..f0681e6de --- /dev/null +++ b/libs/processing-ar/build.gradle @@ -0,0 +1,29 @@ +plugins { + id 'com.android.library' +} + +android { + namespace "processing.ar" + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 33 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation project(':libs:processing-core') + implementation 'com.google.ar:core:1.35.0' +} \ No newline at end of file diff --git a/libs/processing-ar/proguard-rules.pro b/libs/processing-ar/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/libs/processing-ar/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/libs/processing-ar/src/main/AndroidManifest.xml b/libs/processing-ar/src/main/AndroidManifest.xml new file mode 100755 index 000000000..97330b776 --- /dev/null +++ b/libs/processing-ar/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libs/processing-ar/src/main/assets/shaders/ARLightFrag.glsl b/libs/processing-ar/src/main/assets/shaders/ARLightFrag.glsl new file mode 100644 index 000000000..6ab64485c --- /dev/null +++ b/libs/processing-ar/src/main/assets/shaders/ARLightFrag.glsl @@ -0,0 +1,49 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform vec4 colorCorrection; + +varying vec4 vertColor; +varying vec4 backVertColor; + +// Approximate sRGB gamma parameters +const float kGamma = 0.4545454; +const float kMiddleGrayGamma = 0.466; + +void main() { + vec3 colorShift = colorCorrection.rgb; + float averagePixelIntensity = colorCorrection.a; + + vec4 color = gl_FrontFacing ? vertColor : backVertColor; + + // Apply SRGB gamma before writing the fragment color. + color.rgb = pow(color.rgb, vec3(kGamma)); + + // Apply average pixel intensity and color shift + color.rgb *= colorShift * (averagePixelIntensity / kMiddleGrayGamma); + gl_FragColor = color; +} \ No newline at end of file diff --git a/libs/processing-ar/src/main/assets/shaders/ARLightVert.glsl b/libs/processing-ar/src/main/assets/shaders/ARLightVert.glsl new file mode 100644 index 000000000..b9596af48 --- /dev/null +++ b/libs/processing-ar/src/main/assets/shaders/ARLightVert.glsl @@ -0,0 +1,156 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +uniform mat4 modelviewMatrix; +uniform mat4 transformMatrix; +uniform mat3 normalMatrix; + +uniform int lightCount; +uniform vec4 lightPosition[8]; +uniform vec3 lightNormal[8]; +uniform vec3 lightAmbient[8]; +uniform vec3 lightDiffuse[8]; +uniform vec3 lightSpecular[8]; +uniform vec3 lightFalloff[8]; +uniform vec2 lightSpot[8]; + +attribute vec4 position; +attribute vec4 color; +attribute vec3 normal; + +attribute vec4 ambient; +attribute vec4 specular; +attribute vec4 emissive; +attribute float shininess; + +varying vec4 vertColor; +varying vec4 backVertColor; + +const float zero_float = 0.0; +const float one_float = 1.0; +const vec3 zero_vec3 = vec3(0); + +const float kInverseGamma = 2.2; + +float falloffFactor(vec3 lightPos, vec3 vertPos, vec3 coeff) { + vec3 lpv = lightPos - vertPos; + vec3 dist = vec3(one_float); + dist.z = dot(lpv, lpv); + dist.y = sqrt(dist.z); + return one_float / dot(dist, coeff); +} + +float spotFactor(vec3 lightPos, vec3 vertPos, vec3 lightNorm, float minCos, float spotExp) { + vec3 lpv = normalize(lightPos - vertPos); + vec3 nln = -one_float * lightNorm; + float spotCos = dot(nln, lpv); + return spotCos <= minCos ? zero_float : pow(spotCos, spotExp); +} + +float lambertFactor(vec3 lightDir, vec3 vecNormal) { + return max(zero_float, dot(lightDir, vecNormal)); +} + +float blinnPhongFactor(vec3 lightDir, vec3 vertPos, vec3 vecNormal, float shine) { + vec3 np = normalize(vertPos); + vec3 ldp = normalize(lightDir - np); + return pow(max(zero_float, dot(ldp, vecNormal)), shine); +} + +void main() { + // Vertex in clip coordinates + gl_Position = transformMatrix * position; + + // Vertex in eye coordinates + vec3 ecVertex = vec3(modelviewMatrix * position); + + // Normal vector in eye coordinates + vec3 ecNormal = normalize(normalMatrix * normal); + vec3 ecNormalInv = ecNormal * -one_float; + + // Light calculations + vec3 totalAmbient = vec3(0, 0, 0); + + vec3 totalFrontDiffuse = vec3(0, 0, 0); + vec3 totalFrontSpecular = vec3(0, 0, 0); + + vec3 totalBackDiffuse = vec3(0, 0, 0); + vec3 totalBackSpecular = vec3(0, 0, 0); + + for (int i = 0; i < 8; i++) { + if (lightCount == i) break; + + vec3 lightPos = lightPosition[i].xyz; + bool isDir = lightPosition[i].w < one_float; + float spotCos = lightSpot[i].x; + float spotExp = lightSpot[i].y; + + vec3 lightDir; + float falloff; + float spotf; + + if (isDir) { + falloff = one_float; + lightDir = -one_float * lightNormal[i]; + } else { + falloff = falloffFactor(lightPos, ecVertex, lightFalloff[i]); + lightDir = normalize(lightPos - ecVertex); + } + + spotf = spotExp > zero_float ? spotFactor(lightPos, ecVertex, lightNormal[i], + spotCos, spotExp) + : one_float; + + if (any(greaterThan(lightAmbient[i], zero_vec3))) { + totalAmbient += lightAmbient[i] * falloff; + } + + if (any(greaterThan(lightDiffuse[i], zero_vec3))) { + totalFrontDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormal); + totalBackDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormalInv); + } + + if (any(greaterThan(lightSpecular[i], zero_vec3))) { + totalFrontSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormal, shininess); + totalBackSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormalInv, shininess); + } + } + + vec4 gcolor = vec4(pow(color.rgb, vec3(kInverseGamma)), color.a); + vec4 gambient = vec4(pow(ambient.rgb, vec3(kInverseGamma)), ambient.a); + + // Calculating final color as result of all lights (plus emissive term). + // Transparency is determined exclusively by the diffuse component. + vertColor = vec4(totalAmbient, 0) * gambient + + vec4(totalFrontDiffuse, 1) * gcolor + + vec4(totalFrontSpecular, 0) * specular + + vec4(emissive.rgb, 0); + + backVertColor = vec4(totalAmbient, 0) * gambient + + vec4(totalBackDiffuse, 1) * gcolor + + vec4(totalBackSpecular, 0) * specular + + vec4(emissive.rgb, 0); +} \ No newline at end of file diff --git a/libs/processing-ar/src/main/assets/shaders/ARTexLightFrag.glsl b/libs/processing-ar/src/main/assets/shaders/ARTexLightFrag.glsl new file mode 100644 index 000000000..f06fb2160 --- /dev/null +++ b/libs/processing-ar/src/main/assets/shaders/ARTexLightFrag.glsl @@ -0,0 +1,56 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +uniform sampler2D texture; +uniform vec2 texOffset; +uniform vec4 colorCorrection; + +varying vec4 vertColor; +varying vec4 backVertColor; +varying vec4 vertTexCoord; + +// Approximate sRGB gamma parameters +const float kGamma = 0.4545454; +const float kInverseGamma = 2.2; +const float kMiddleGrayGamma = 0.466; + +void main() { + vec3 colorShift = colorCorrection.rgb; + float averagePixelIntensity = colorCorrection.a; + + vec4 tex = texture2D(texture, vertTexCoord.st); + vec4 gtex = vec4(pow(tex.rgb, vec3(kInverseGamma)), tex.a); + + vec4 color = gtex * (gl_FrontFacing ? vertColor : backVertColor); + + // Apply SRGB gamma before writing the fragment color. + color.rgb = pow(color.rgb, vec3(kGamma)); + + // Apply average pixel intensity and color shift + color.rgb *= colorShift * (averagePixelIntensity / kMiddleGrayGamma); + gl_FragColor = color; +} \ No newline at end of file diff --git a/libs/processing-ar/src/main/assets/shaders/ARTexLightVert.glsl b/libs/processing-ar/src/main/assets/shaders/ARTexLightVert.glsl new file mode 100644 index 000000000..5269a550e --- /dev/null +++ b/libs/processing-ar/src/main/assets/shaders/ARTexLightVert.glsl @@ -0,0 +1,162 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +uniform mat4 modelviewMatrix; +uniform mat4 transformMatrix; +uniform mat3 normalMatrix; +uniform mat4 texMatrix; + +uniform int lightCount; +uniform vec4 lightPosition[8]; +uniform vec3 lightNormal[8]; +uniform vec3 lightAmbient[8]; +uniform vec3 lightDiffuse[8]; +uniform vec3 lightSpecular[8]; +uniform vec3 lightFalloff[8]; +uniform vec2 lightSpot[8]; + +attribute vec4 position; +attribute vec4 color; +attribute vec3 normal; +attribute vec2 texCoord; + +attribute vec4 ambient; +attribute vec4 specular; +attribute vec4 emissive; +attribute float shininess; + +varying vec4 vertColor; +varying vec4 backVertColor; +varying vec4 vertTexCoord; + +const float zero_float = 0.0; +const float one_float = 1.0; +const vec3 zero_vec3 = vec3(0); + +const float kInverseGamma = 2.2; + +float falloffFactor(vec3 lightPos, vec3 vertPos, vec3 coeff) { + vec3 lpv = lightPos - vertPos; + vec3 dist = vec3(one_float); + dist.z = dot(lpv, lpv); + dist.y = sqrt(dist.z); + return one_float / dot(dist, coeff); +} + +float spotFactor(vec3 lightPos, vec3 vertPos, vec3 lightNorm, float minCos, float spotExp) { + vec3 lpv = normalize(lightPos - vertPos); + vec3 nln = -one_float * lightNorm; + float spotCos = dot(nln, lpv); + return spotCos <= minCos ? zero_float : pow(spotCos, spotExp); +} + +float lambertFactor(vec3 lightDir, vec3 vecNormal) { + return max(zero_float, dot(lightDir, vecNormal)); +} + +float blinnPhongFactor(vec3 lightDir, vec3 vertPos, vec3 vecNormal, float shine) { + vec3 np = normalize(vertPos); + vec3 ldp = normalize(lightDir - np); + return pow(max(zero_float, dot(ldp, vecNormal)), shine); +} + +void main() { + // Vertex in clip coordinates + gl_Position = transformMatrix * position; + + // Vertex in eye coordinates + vec3 ecVertex = vec3(modelviewMatrix * position); + + // Normal vector in eye coordinates + vec3 ecNormal = normalize(normalMatrix * normal); + vec3 ecNormalInv = ecNormal * -one_float; + + // Light calculations + vec3 totalAmbient = vec3(0, 0, 0); + + vec3 totalFrontDiffuse = vec3(0, 0, 0); + vec3 totalFrontSpecular = vec3(0, 0, 0); + + vec3 totalBackDiffuse = vec3(0, 0, 0); + vec3 totalBackSpecular = vec3(0, 0, 0); + + for (int i = 0; i < 8; i++) { + if (lightCount == i) break; + + vec3 lightPos = lightPosition[i].xyz; + bool isDir = lightPosition[i].w < one_float; + float spotCos = lightSpot[i].x; + float spotExp = lightSpot[i].y; + + vec3 lightDir; + float falloff; + float spotf; + + if (isDir) { + falloff = one_float; + lightDir = -one_float * lightNormal[i]; + } else { + falloff = falloffFactor(lightPos, ecVertex, lightFalloff[i]); + lightDir = normalize(lightPos - ecVertex); + } + + spotf = spotExp > zero_float ? spotFactor(lightPos, ecVertex, lightNormal[i], + spotCos, spotExp) + : one_float; + + if (any(greaterThan(lightAmbient[i], zero_vec3))) { + totalAmbient += lightAmbient[i] * falloff; + } + + if (any(greaterThan(lightDiffuse[i], zero_vec3))) { + totalFrontDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormal); + totalBackDiffuse += lightDiffuse[i] * falloff * spotf * + lambertFactor(lightDir, ecNormalInv); + } + + if (any(greaterThan(lightSpecular[i], zero_vec3))) { + totalFrontSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormal, shininess); + totalBackSpecular += lightSpecular[i] * falloff * spotf * + blinnPhongFactor(lightDir, ecVertex, ecNormalInv, shininess); + } + } + + vec4 gcolor = vec4(pow(color.rgb, vec3(kInverseGamma)), color.a); + vec4 gambient = vec4(pow(ambient.rgb, vec3(kInverseGamma)), ambient.a); + + // Calculating final color as result of all lights (plus emissive term). + // Transparency is determined exclusively by the diffuse component. + vertColor = vec4(totalAmbient, 0) * gambient + + vec4(totalFrontDiffuse, 1) * gcolor + + vec4(totalFrontSpecular, 0) * specular + + vec4(emissive.rgb, 0); + + backVertColor = vec4(totalAmbient, 0) * gambient + + vec4(totalBackDiffuse, 1) * gcolor + + vec4(totalBackSpecular, 0) * specular + + vec4(emissive.rgb, 0); + + // Calculating texture coordinates, with r and q set both to one + vertTexCoord = texMatrix * vec4(texCoord, 1.0, 1.0); +} diff --git a/libs/processing-ar/src/main/assets/shaders/BackgroundFrag.glsl b/libs/processing-ar/src/main/assets/shaders/BackgroundFrag.glsl new file mode 100644 index 000000000..d2fc66d60 --- /dev/null +++ b/libs/processing-ar/src/main/assets/shaders/BackgroundFrag.glsl @@ -0,0 +1,9 @@ +#extension GL_OES_EGL_image_external : require + +precision mediump float; +varying vec2 v_TexCoord; +uniform samplerExternalOES sTexture; + +void main() { + gl_FragColor = texture2D(sTexture, v_TexCoord); +} diff --git a/libs/processing-ar/src/main/assets/shaders/BackgroundVert.glsl b/libs/processing-ar/src/main/assets/shaders/BackgroundVert.glsl new file mode 100644 index 000000000..25a4da170 --- /dev/null +++ b/libs/processing-ar/src/main/assets/shaders/BackgroundVert.glsl @@ -0,0 +1,9 @@ +attribute vec4 a_Position; +attribute vec2 a_TexCoord; + +varying vec2 v_TexCoord; + +void main() { + gl_Position = a_Position; + v_TexCoord = a_TexCoord; +} diff --git a/libs/processing-ar/src/main/java/processing/ar/ARAnchor.java b/libs/processing-ar/src/main/java/processing/ar/ARAnchor.java new file mode 100644 index 000000000..c3cd930c4 --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/ARAnchor.java @@ -0,0 +1,87 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.ar; + +import processing.core.PMatrix3D; + +public class ARAnchor { + protected ARGraphics g; + private boolean disposed = false; + + private int id; + private PMatrix3D m; + + public ARAnchor(ARTrackable trackable, float x, float y, float z) { + this.g = trackable.g; + + int idx = g.trackableIndex(Integer.parseInt(trackable.id())); + id = g.createAnchor(idx, x, y, z); + } + + public ARAnchor(ARTrackable trackable) { + this.g = trackable.g; + id = g.createAnchor(trackable.hit); + trackable.hit = null; + } + + public void dispose() { + if (!disposed) { + g.deleteAnchor(id); + disposed = true; + } + } + + public String id() { + return String.valueOf(id); + } + + public PMatrix3D matrix() { + m = g.getTrackableMatrix(id, m); + return m; + } + + public void attach() { + g.pushMatrix(); + g.anchor(id); + } + + public void detach() { + g.popMatrix(); + } + + public boolean isTracking() { + return g.anchorStatus(id) == ARGraphics.TRACKING; + } + + public boolean isPaused() { + return g.anchorStatus(id) == ARGraphics.PAUSED; + } + + public boolean isStopped() { + return g.anchorStatus(id) == ARGraphics.STOPPED; + } + + public boolean isDisposed() { + return disposed; + } +} diff --git a/libs/processing-ar/src/main/java/processing/ar/ARGraphics.java b/libs/processing-ar/src/main/java/processing/ar/ARGraphics.java new file mode 100644 index 000000000..3cdfba0b9 --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/ARGraphics.java @@ -0,0 +1,687 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019-23 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.ar; + +import android.view.SurfaceHolder; + +import com.google.ar.core.Anchor; +import com.google.ar.core.AugmentedImage; +import com.google.ar.core.HitResult; +import com.google.ar.core.Plane; +import com.google.ar.core.Pose; +import com.google.ar.core.Trackable; +import com.google.ar.core.TrackingState; +import com.google.ar.core.Config; +import com.google.ar.core.Session; + +import java.net.URL; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; + +import processing.android.AppComponent; +import processing.core.PGraphics; +import processing.core.PMatrix3D; +import processing.core.PSurface; +import processing.opengl.PGL; +import processing.opengl.PGLES; +import processing.opengl.PGraphics3D; +import processing.opengl.PGraphicsOpenGL; +import processing.opengl.PShader; + +public class ARGraphics extends PGraphics3D { + static protected final int UNKNOWN = -1; + + static protected final int PLANE_FLOOR = 0; + static protected final int PLANE_CEILING = 1; + static protected final int PLANE_WALL = 2; + static protected final int POINT = 3; + static protected final int IMAGE = 4; + + static protected final int TRACKING = 0; + static protected final int PAUSED = 1; + static protected final int STOPPED = 2; + + // Convenience reference to the AR surface. It is the same object one gets from PApplet.getSurface(). + protected ARSurface surfar; + + protected BackgroundRenderer backgroundRenderer; + + protected float[] projMatrix = new float[16]; + protected float[] viewMatrix = new float[16]; + protected float[] anchorMatrix = new float[16]; + protected float[] colorCorrection = new float[4]; + + protected ArrayList trackers = new ArrayList(); + protected ArrayList trackObjects = new ArrayList(); + protected HashMap trackMatrices = new HashMap(); + protected HashMap trackIds = new HashMap(); + protected HashMap trackIdx = new HashMap(); + + protected ArrayList newObjects = new ArrayList(); + protected ArrayList delAnchors = new ArrayList(); + + protected HashMap anchors = new HashMap(); + + protected float[] pointIn = new float[3]; + protected float[] pointOut = new float[3]; + + protected int lastTrackableId = 0; + protected int lastAnchorId = 0; + + static protected URL arLightShaderVertURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/ARLightVert.glsl"); + static protected URL arTexlightShaderVertURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/ARTexLightVert.glsl"); + static protected URL arLightShaderFragURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/ARLightFrag.glsl"); + static protected URL arTexlightShaderFragURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/ARTexLightFrag.glsl"); + + protected PShader arLightShader; + protected PShader arTexlightShader; + + + public ARGraphics() { + } + + + static ARTrackable[] getTrackables() { + return null; + } + + @Override + public PSurface createSurface(AppComponent appComponent, SurfaceHolder surfaceHolder, boolean reset) { + if (reset) pgl.resetFBOLayer(); + surfar = new ARSurface(this, appComponent, surfaceHolder); + return surfar; + } + + + @Override + protected PGL createPGL(PGraphicsOpenGL pGraphicsOpenGL) { + return new PGLES(pGraphicsOpenGL); + } + + + @Override + public void eye() { + super.ortho(0, width, -height, 0, -1, +1); + + eyeDist = 1; + resetMatrix(); + } + + @Override + public void beginDraw() { + super.beginDraw(); + updateView(); + + // Always clear the screen and draw the background + background(0); + backgroundRenderer.draw(surfar.frame); + } + + public void endDraw() { + cleanup(); + super.endDraw(); + } + + + @Override + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + PGraphics.showWarning("The camera cannot be set in AR"); + } + + + @Override + public void perspective(float fov, float aspect, float zNear, float zFar) { + PGraphics.showWarning("Perspective cannot be set in AR"); + } + + + @Override + protected void defaultCamera() { + // do nothing + } + + + @Override + protected void defaultPerspective() { + // do nothing + } + + + @Override + protected void saveState() { + } + + + @Override + protected void restoreState() { + } + + + @Override + protected void restoreSurface() { + } + + + protected void updateView() { + if (projMatrix != null && viewMatrix != null) { + + // Fist, set all matrices to identity + resetProjection(); + resetMatrix(); + + // Apply the projection matrix + applyProjection(projMatrix[0], projMatrix[4], projMatrix[8], projMatrix[12], + projMatrix[1], projMatrix[5], projMatrix[9], projMatrix[13], + projMatrix[2], projMatrix[6], projMatrix[10], projMatrix[14], + projMatrix[3], projMatrix[7], projMatrix[11], projMatrix[15]); + + // make modelview = view + applyMatrix(viewMatrix[0], viewMatrix[4], viewMatrix[8], viewMatrix[12], + viewMatrix[1], viewMatrix[5], viewMatrix[9], viewMatrix[13], + viewMatrix[2], viewMatrix[6], viewMatrix[10], viewMatrix[14], + viewMatrix[3], viewMatrix[7], viewMatrix[11], viewMatrix[15]); + } + } + + + public void addTracker(ARTracker tracker) { + trackers.add(tracker); + } + + + public void removeTracker(ARTracker tracker) { + trackers.remove(tracker); + } + + + public int trackableCount() { + return trackObjects.size(); + } + + + public int trackableId(int i) { + return trackIds.get(trackObjects.get(i)); + } + + + public int trackableIndex(int id) { + return trackIdx.get(id); + } + + + public String trackableName(int i) { + Trackable track = trackObjects.get(i); + if (track instanceof AugmentedImage) { + AugmentedImage img = ((AugmentedImage)track); + return img.getName(); + } + return null; + } + + + public int trackableType(int i) { + Trackable track = trackObjects.get(i); + if (track instanceof Plane) { + Plane plane = (Plane)track; + if (plane.getType() == Plane.Type.HORIZONTAL_UPWARD_FACING) { + return PLANE_FLOOR; + } else if (plane.getType() == Plane.Type.HORIZONTAL_DOWNWARD_FACING) { + return PLANE_CEILING; + } else if (plane.getType() == Plane.Type.VERTICAL) { + return PLANE_WALL; + } + } else if (track instanceof AugmentedImage) { + return IMAGE; + } + return UNKNOWN; + } + + + public int trackableStatus(int i) { + Trackable track = trackObjects.get(i); + if (track.getTrackingState() == TrackingState.PAUSED) { + return PAUSED; + } else if (track.getTrackingState() == TrackingState.TRACKING) { + return TRACKING; + } else if (track.getTrackingState() == TrackingState.STOPPED) { + return STOPPED; + } + return UNKNOWN; + } + + + public boolean trackableNew(int i) { + Trackable track = trackObjects.get(i); + return newObjects.contains(track); + } + + + public boolean trackableSelected(int i, int mx, int my) { + Trackable tracki = trackObjects.get(i); + for (HitResult hit : surfar.frame.hitTest(mx, my)) { + Trackable trackable = hit.getTrackable(); + Pose hitPose = hit.getHitPose(); + if (trackable instanceof Plane) { + Plane plane = (Plane)trackable; + if (tracki.equals(plane) && plane.isPoseInPolygon(hitPose)) { + return true; + } + } else if (trackable instanceof AugmentedImage) { + AugmentedImage image = (AugmentedImage)trackable; + Pose anchorPose = image.getCenterPose(); + Pose localHitPose = anchorPose.compose(hitPose); + if (tracki.equals(image) && isPoseInsideAugmentedImage(localHitPose, image)) { + return true; + } + } + } + return false; + } + + + protected HitResult getHitResult(int mx, int my) { + for (HitResult hit : surfar.frame.hitTest(mx, my)) { + Trackable trackable = hit.getTrackable(); + Pose hitPose = hit.getHitPose(); + if (trackable instanceof Plane) { + Plane plane = (Plane)trackable; + if (trackObjects.contains(plane) && plane.isPoseInPolygon(hitPose)) { + return hit; + } + } else if (trackable instanceof AugmentedImage) { + AugmentedImage image = (AugmentedImage)trackable; + Pose anchorPose = image.getCenterPose(); + Pose localHitPose = anchorPose.compose(hitPose); + if (trackObjects.contains(image) && isPoseInsideAugmentedImage(localHitPose, image)) { + return hit; + } + } + } + return null; + } + + + private boolean isPoseInsideAugmentedImage(Pose pose, AugmentedImage image) { + // Get the four corners of the AugmentedImage's defining rectangle + float[] corners = new float[16]; + image.getCenterPose().toMatrix(corners, 0); + + // Define the vertices of the rectangle in 2D (assuming the image is flat on the XZ plane) + float imageMinX = Float.POSITIVE_INFINITY; + float imageMaxX = Float.NEGATIVE_INFINITY; + float imageMinZ = Float.POSITIVE_INFINITY; + float imageMaxZ = Float.NEGATIVE_INFINITY; + + // Extract the X and Z coordinates of the corners + for (int i = 0; i < 8; i += 2) { + float cornerX = corners[i]; + float cornerZ = corners[i + 2]; + + if (cornerX < imageMinX) { + imageMinX = cornerX; + } + if (cornerX > imageMaxX) { + imageMaxX = cornerX; + } + if (cornerZ < imageMinZ) { + imageMinZ = cornerZ; + } + if (cornerZ > imageMaxZ) { + imageMaxZ = cornerZ; + } + } + + // Check if the Pose's position (X, Z) is within the bounds of the AugmentedImage's rectangle + float poseX = pose.tx(); + float poseZ = pose.tz(); + return (imageMinX <= poseX && poseX <= imageMaxX && imageMinZ <= poseZ && poseZ <= imageMaxZ); + } + + + protected int getTrackable(HitResult hit) { + Trackable track = hit.getTrackable(); + return trackObjects.indexOf(track); + } + + + public float[] getTrackablePolygon(int i) { + return getTrackablePolygon(i, null); + } + + + public float[] getTrackablePolygon(int i, float[] points) { + Trackable track = trackObjects.get(i); + if (track instanceof Plane) { + Plane plane = (Plane)track; + FloatBuffer buffer = plane.getPolygon(); + buffer.rewind(); + if (points == null || points.length < buffer.capacity()) { + points = new float[buffer.capacity()]; + } + buffer.get(points, 0, buffer.capacity()); + } else if (track instanceof AugmentedImage) { + AugmentedImage image = (AugmentedImage)track; + points = new float[8]; + image.getCenterPose().toMatrix(points, 0); + } + return points; + } + + + public float getTrackableExtentX(int i) { + Trackable track = trackObjects.get(i); + if (track instanceof Plane) { + return ((Plane)track).getExtentX(); + } else if (track instanceof AugmentedImage) { + return ((AugmentedImage)track).getExtentX(); + } + return -1; + } + + + public float getTrackableExtentZ(int i) { + Trackable track = trackObjects.get(i); + if (track instanceof Plane) { + return ((Plane)track).getExtentZ(); + } else if (track instanceof AugmentedImage) { + return ((AugmentedImage)track).getExtentZ(); + } + return -1; + } + + + public PMatrix3D getTrackableMatrix(int i) { + return getTrackableMatrix(i, null); + } + + + public PMatrix3D getTrackableMatrix(int i, PMatrix3D target) { + if (target == null) { + target = new PMatrix3D(); + } + + Plane plane = (Plane)trackObjects.get(i); + float[] mat = trackMatrices.get(plane); + target.set(mat[0], mat[4], mat[8], mat[12], + mat[1], mat[5], mat[9], mat[13], + mat[2], mat[6], mat[10], mat[14], + mat[3], mat[7], mat[11], mat[15]); + + return target; + } + + + public int createAnchor(int i, float x, float y, float z) { + Trackable track = trackObjects.get(i); + Pose centerPose = null; + if (track instanceof Plane) { + Plane plane = (Plane)track; + centerPose = plane.getCenterPose(); + } else if (track instanceof AugmentedImage) { + AugmentedImage img = (AugmentedImage)track; + centerPose = img.getCenterPose(); + } + if (centerPose != null) { + pointIn[0] = x; + pointIn[1] = y; + pointIn[2] = z; + centerPose.transformPoint(pointIn, 0, pointOut, 0); + Pose anchorPose = Pose.makeTranslation(pointOut); + Anchor anchor = track.createAnchor(anchorPose); + anchors.put(++lastAnchorId, anchor); + return lastAnchorId; + } + return -1; + } + + + public int createAnchor(int mx, int my) { + for (HitResult hit : surfar.frame.hitTest(mx, my)) { + Trackable trackable = hit.getTrackable(); + Pose hitPose = hit.getHitPose(); + if (trackable instanceof Plane) { + Plane plane = (Plane)trackable; + if (trackObjects.contains(plane) && plane.isPoseInPolygon(hitPose)) { + return createAnchor(hit); + } + } else if (trackable instanceof AugmentedImage) { + AugmentedImage image = (AugmentedImage)trackable; + Pose anchorPose = image.getCenterPose(); + Pose localHitPose = anchorPose.compose(hitPose); + if (trackObjects.contains(image) && isPoseInsideAugmentedImage(localHitPose, image)) { + return createAnchor(hit); + } + } + } + return 0; + } + + + protected int createAnchor(HitResult hit) { + Anchor anchor = hit.createAnchor(); + anchors.put(++lastAnchorId, anchor); + return lastAnchorId; + } + + + public void deleteAnchor(int id) { + delAnchors.add(id); + } + + + public int anchorCount() { + return anchors.size(); + } + + + public int anchorStatus(int id) { + Anchor anchor = anchors.get(id); + if (anchor.getTrackingState() == TrackingState.PAUSED) { + return PAUSED; + } else if (anchor.getTrackingState() == TrackingState.TRACKING) { + return TRACKING; + } else if (anchor.getTrackingState() == TrackingState.STOPPED) { + return STOPPED; + } + return UNKNOWN; + } + + + public PMatrix3D getAnchorMatrix(int id) { + return getAnchorMatrix(id, null); + } + + + public PMatrix3D getAnchorMatrix(int id, PMatrix3D target) { + if (target == null) { + target = new PMatrix3D(); + } + Anchor anchor = anchors.get(id); + anchor.getPose().toMatrix(anchorMatrix, 0); + target.set(anchorMatrix[0], anchorMatrix[4], anchorMatrix[8], anchorMatrix[12], + anchorMatrix[1], anchorMatrix[5], anchorMatrix[9], anchorMatrix[13], + anchorMatrix[2], anchorMatrix[6], anchorMatrix[10], anchorMatrix[14], + anchorMatrix[3], anchorMatrix[7], anchorMatrix[11], anchorMatrix[15]); + return target; + } + + + public void anchor(int id) { + Anchor anchor = anchors.get(id); + anchor.getPose().toMatrix(anchorMatrix, 0); + + // now, modelview = view * anchor + applyMatrix(anchorMatrix[0], anchorMatrix[4], anchorMatrix[8], anchorMatrix[12], + anchorMatrix[1], anchorMatrix[5], anchorMatrix[9], anchorMatrix[13], + anchorMatrix[2], anchorMatrix[6], anchorMatrix[10], anchorMatrix[14], + anchorMatrix[3], anchorMatrix[7], anchorMatrix[11], anchorMatrix[15]); + } + + + protected void createBackgroundRenderer() { + backgroundRenderer = new BackgroundRenderer(surfar.getActivity()); + } + + + protected void setCameraTexture() { + surfar.session.setCameraTextureName(backgroundRenderer.getTextureId()); + } + + + protected void updateMatrices() { + surfar.camera.getProjectionMatrix(projMatrix, 0, 0.1f, 100.0f); + surfar.camera.getViewMatrix(viewMatrix, 0); + surfar.frame.getLightEstimate().getColorCorrection(colorCorrection, 0); + } + + + protected void updateTrackables() { + Collection planes = surfar.frame.getUpdatedTrackables(Plane.class); + for (Plane plane: planes) { + addNewPlane(plane); + } + + Collection images = surfar.frame.getUpdatedTrackables(AugmentedImage.class); + for (AugmentedImage image: images) { + addNewImage(image); + } + + // Remove stopped and subsummed trackables + for (int i = trackObjects.size() - 1; i >= 0; i--) { + Trackable track = trackObjects.get(i); + if (track instanceof Plane) { + Plane plane = (Plane)track; + if (plane.getTrackingState() == TrackingState.STOPPED || plane.getSubsumedBy() != null) { + trackObjects.remove(i); + trackMatrices.remove(plane); + int pid = trackIds.remove(plane); + trackIdx.remove(pid); + for (ARTracker t: trackers) t.remove(pid); + } + } + } + + // Update indices + for (int i = 0; i < trackObjects.size(); i++) { + Trackable track = trackObjects.get(i); + int pid = trackIds.get(track); + trackIdx.put(pid, i); + if (newObjects.contains(track)) { + for (ARTracker t: trackers) t.create(i); + } + } + } + + + protected void addNewPlane(Plane plane) { + if (plane.getSubsumedBy() != null) return; + float[] mat = addNewMatrix(plane); + Pose pose = plane.getCenterPose(); + if (pose != null) pose.toMatrix(mat, 0); + } + + + protected void addNewImage(AugmentedImage image) { + float[] mat = addNewMatrix(image); + Pose pose = image.getCenterPose(); + if (pose != null) pose.toMatrix(mat, 0); + } + + + protected float[] addNewMatrix(Trackable obj) { + float[] mat; + if (trackMatrices.containsKey(obj)) { + mat = trackMatrices.get(obj); + } else { + mat = new float[16]; + trackMatrices.put(obj, mat); + trackObjects.add(obj); + trackIds.put(obj, ++lastTrackableId); + newObjects.add(obj); + } + return mat; + } + + + protected void cleanup() { + newObjects.clear(); + for (int id: delAnchors) { + Anchor anchor = anchors.remove(id); + anchor.detach(); + } + delAnchors.clear(); + } + + + @Override + protected PShader getPolyShader(boolean lit, boolean tex) { + if (getPrimaryPG() != this) { + // An offscreen surface will use the default shaders from the parent OpenGL renderer + return super.getPolyShader(lit, tex); + } + + PShader shader; + boolean useDefault = polyShader == null; + if (lit) { + if (tex) { + if (useDefault || !isPolyShaderTexLight(polyShader)) { + if (arTexlightShader == null) { + arTexlightShader = loadShaderFromURL(arTexlightShaderFragURL, arTexlightShaderVertURL); + } + shader = arTexlightShader; + } else { + shader = polyShader; + } + } else { + if (useDefault || !isPolyShaderLight(polyShader)) { + if (arLightShader == null) { + arLightShader = loadShaderFromURL(arLightShaderFragURL, arLightShaderVertURL); + } + shader = arLightShader; + } else { + shader = polyShader; + } + } + updateShader(shader); + return shader; + } else { + // Non-lit shaders use the default shaders from the parent OpenGL renderer + return super.getPolyShader(lit, tex); + } + } + + + @Override + protected void updateShader(PShader shader) { + super.updateShader(shader); + shader.set("colorCorrection", colorCorrection, 4); + } +} diff --git a/libs/processing-ar/src/main/java/processing/ar/ARSurface.java b/libs/processing-ar/src/main/java/processing/ar/ARSurface.java new file mode 100644 index 000000000..e1a4ba08f --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/ARSurface.java @@ -0,0 +1,332 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.ar; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ConfigurationInfo; +import android.content.res.AssetManager; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.view.*; + +import com.google.ar.core.*; +import com.google.ar.core.exceptions.*; + +import processing.android.AppComponent; +import processing.core.PGraphics; +import processing.opengl.PGLES; +import processing.opengl.PGraphicsOpenGL; +import processing.opengl.PSurfaceGLES; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import java.io.File; +import java.io.InputStream; + +public class ARSurface extends PSurfaceGLES { + private static String T_ALERT_MESSAGE = "ALERT"; + private static String C_NOT_SUPPORTED = "ARCore SDK required to run this app type"; + private static String T_PROMPT_MESSAGE = "PROMPT"; + private static String C_SUPPORTED = "ARCore SDK is installed"; + private static String C_EXCEPT_INSTALL = "Please install ARCore"; + private static String C_EXCEPT_UPDATE_SDK = "Please update ARCore"; + private static String C_EXCEPT_UPDATE_APP = "Please update this app"; + private static String C_DEVICE = "This device does not support AR"; + + // Made these public so they can be accessed from the sketch + public Session session; + public Frame frame; + public Camera camera; + + protected GLSurfaceView surfaceView; + protected AndroidARRenderer renderer; + protected ARGraphics par; + + protected RotationHandler displayRotationHelper; + + public ARSurface(PGraphics graphics, AppComponent appComponent, SurfaceHolder surfaceHolder) { + super(graphics, appComponent, surfaceHolder); + this.sketch = graphics.parent; + this.graphics = graphics; + this.component = appComponent; + this.pgl = (PGLES) ((PGraphicsOpenGL) graphics).pgl; + + par = (ARGraphics) graphics; + + displayRotationHelper = new RotationHandler(activity); + surfaceView = new SurfaceViewAR(activity); + } + + @Override + public Context getContext() { + return activity; + } + + @Override + public void finish() { + sketch.getActivity().finish(); + } + + @Override + public AssetManager getAssets() { + return sketch.getContext().getAssets(); + } + + @Override + public void startActivity(Intent intent) { + sketch.getContext().startActivity(intent); + } + + @Override + public void initView(int sketchWidth, int sketchHeight) { + Window window = sketch.getActivity().getWindow(); + + window.getDecorView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + window.setContentView(surfaceView); + } + + @Override + public String getName() { + return sketch.getActivity().getComponentName().getPackageName(); + } + + @Override + public void setOrientation(int which) { + } + + @Override + public File getFilesDir() { + return sketch.getActivity().getFilesDir(); + } + + @Override + public InputStream openFileInput(String filename) { + return null; + } + + @Override + public File getFileStreamPath(String path) { + return sketch.getActivity().getFileStreamPath(path); + } + + @Override + public void dispose() { + } + + + public class SurfaceViewAR extends GLSurfaceView { + public SurfaceViewAR(Context context) { + super(context); + + final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo(); + final boolean supportsGLES2 = configurationInfo.reqGlEsVersion >= 0x20000; + + if (!supportsGLES2) { + throw new RuntimeException("OpenGL ES 2.0 is not supported by this device."); + } + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + setPreserveEGLContextOnPause(true); + setEGLContextClientVersion(2); + setEGLConfigChooser(8, 8, 8, 8, 16, 0); + setRenderer(getARRenderer()); + setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY); + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + return sketch.surfaceTouchEvent(event); + } + + + @Override + public boolean onKeyDown(int code, android.view.KeyEvent event) { + sketch.surfaceKeyDown(code, event); + return super.onKeyDown(code, event); + } + + + @Override + public boolean onKeyUp(int code, android.view.KeyEvent event) { + sketch.surfaceKeyUp(code, event); + return super.onKeyUp(code, event); + } + } + + public AndroidARRenderer getARRenderer() { + renderer = new AndroidARRenderer(); + return renderer; + } + + protected class AndroidARRenderer implements GLSurfaceView.Renderer { + public AndroidARRenderer() { + } + + @Override + public void onSurfaceCreated(GL10 gl, EGLConfig config) { + pgl.getGL(null); + par.createBackgroundRenderer(); + } + + @Override + public void onSurfaceChanged(GL10 gl, int width, int height) { + displayRotationHelper.onSurfaceChanged(width, height); + GLES20.glViewport(0, 0, width, height); + + sketch.surfaceChanged(); + graphics.surfaceChanged(); + + sketch.setSize(width, height); + graphics.setSize(sketch.sketchWidth(), sketch.sketchHeight()); + } + + @Override + public void onDrawFrame(GL10 gl) { + if (session == null) return; + + displayRotationHelper.updateSessionIfNeeded(session); + try { + + par.setCameraTexture(); + frame = session.update(); + camera = frame.getCamera(); + + if (camera.getTrackingState() == TrackingState.TRACKING) par.updateTrackables(); + par.updateMatrices(); + + sketch.calculate(); + sketch.handleDraw(); + + + } catch (Throwable tr) { + PGraphics.showWarning("An error occurred in ARCORE: " + tr.getMessage()); + } + } + } + + + @Override + public void startThread() { + } + + @Override + public void pauseThread() { + if (session != null) { + displayRotationHelper.onPause(); + surfaceView.onPause(); + session.pause(); + } + } + + @Override + public void resumeThread() { + if (!sketch.hasPermission("android.permission.CAMERA")) return; + + if (session == null) { + String message = null; + String exception = null; + try { + // Perhaps this should be done in the MainActivity? + // https://github.com/google-ar/arcore-android-sdk/blob/master/samples/hello_ar_java/app/src/main/java/com/google/ar/core/examples/java/helloar/HelloArActivity.java + switch (ArCoreApk.getInstance().requestInstall(sketch.getActivity(), true)) { + case INSTALL_REQUESTED: + message(T_ALERT_MESSAGE, C_NOT_SUPPORTED); + return; + case INSTALLED: + break; + } + + session = new Session(activity); + } catch (UnavailableArcoreNotInstalledException + | UnavailableUserDeclinedInstallationException e) { + message = C_EXCEPT_INSTALL; + exception = e.toString(); + } catch (UnavailableApkTooOldException e) { + message = C_EXCEPT_UPDATE_SDK; + exception = e.toString(); + } catch (UnavailableSdkTooOldException e) { + message = C_EXCEPT_UPDATE_APP; + exception = e.toString(); + } catch (Exception e) { + e.printStackTrace(); + System.out.println("That's that"); + } + + if (message != null) { + message(T_ALERT_MESSAGE, message + " -- " + exception); + } + + Config config = new Config(session); + if (!session.isSupported(config)) { + message(T_PROMPT_MESSAGE, C_DEVICE); + } + session.configure(config); + } + try { + session.resume(); + } catch (CameraNotAvailableException e) { + } + surfaceView.onResume(); + displayRotationHelper.onResume(); + } + + public void message(String _title, String _message) { + final Activity parent = activity; + final String message = _message; + final String title = _title; + + parent.runOnUiThread(new Runnable() { + public void run() { + new AlertDialog.Builder(parent) + .setTitle(title) + .setMessage(message) + .setPositiveButton("OK", + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + } + }).show(); + } + }); + } +} diff --git a/libs/processing-ar/src/main/java/processing/ar/ARTrackable.java b/libs/processing-ar/src/main/java/processing/ar/ARTrackable.java new file mode 100644 index 000000000..c939b95d8 --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/ARTrackable.java @@ -0,0 +1,134 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.ar; + +import processing.core.PMatrix3D; + +import com.google.ar.core.HitResult; + +public class ARTrackable { + protected ARGraphics g; + protected HitResult hit; + + private String name; + private int id; + private PMatrix3D m; + private float[] points; + + public ARTrackable(ARGraphics g, int id, String name) { + this.g = g; + this.id = id; + this.name = name; + } + + public String id() { + return String.valueOf(id); + } + + public PMatrix3D matrix() { + int idx = g.trackableIndex(id); + m = g.getTrackableMatrix(idx, m); + return m; + } + + public void transform() { + g.applyMatrix(matrix()); + } + + public float[] getPolygon() { + int idx = g.trackableIndex(id); + points = g.getTrackablePolygon(idx, points); + return points; + } + + public float lengthX() { + int idx = g.trackableIndex(id); + return g.getTrackableExtentX(idx); + } + + public float lengthY() { + return 0; + } + + public float lengthZ() { + int idx = g.trackableIndex(id); + return g.getTrackableExtentZ(idx); + } + + public String getName() { + return name; + } + + public boolean isSelected(int mx, int my) { + int idx = g.trackableIndex(id); + return g.trackableSelected(idx, mx, my); + } + + public boolean isNew() { + int idx = g.trackableIndex(id); + return g.trackableNew(idx); + } + + public boolean isTracking() { + int idx = g.trackableIndex(id); + return g.trackableStatus(idx) == ARGraphics.TRACKING; + } + + public boolean isPaused() { + int idx = g.trackableIndex(id); + return g.trackableStatus(idx) == ARGraphics.PAUSED; + } + + public boolean isStopped() { + int idx = g.trackableIndex(id); + return g.trackableStatus(idx) == ARGraphics.STOPPED; + } + + public boolean isPlane() { + return true; + } + + public boolean isPointCloud() { + return false; + } + + public boolean isFloorPlane() { + int idx = g.trackableIndex(id); + return g.trackableType(idx) == ARGraphics.PLANE_FLOOR; + } + + public boolean isImage(){ + int idx = g.trackableIndex(id); + return g.trackableType(idx)== ARGraphics.IMAGE; + } + + public boolean isCeilingPlane() { + int idx = g.trackableIndex(id); + return g.trackableType(idx) == ARGraphics.PLANE_CEILING; + } + + public boolean isWallPlane() { + int idx = g.trackableIndex(id); + return g.trackableType(idx) == ARGraphics.PLANE_WALL; + } +} diff --git a/libs/processing-ar/src/main/java/processing/ar/ARTracker.java b/libs/processing-ar/src/main/java/processing/ar/ARTracker.java new file mode 100644 index 000000000..9beb70bd7 --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/ARTracker.java @@ -0,0 +1,183 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.ar; + +import android.graphics.Bitmap; + +import com.google.ar.core.HitResult; +import com.google.ar.core.AugmentedImageDatabase; +import com.google.ar.core.Config; +import com.google.ar.core.Session; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.Set; + +import processing.core.PApplet; +import processing.core.PImage; + +public class ARTracker { + protected PApplet p; + protected ARGraphics g; + protected AugmentedImageDatabase db; + + private HashMap trackables = new HashMap(); + private ArrayList toRemove = new ArrayList(); + private Method trackableEventMethod; + + public ARTracker(PApplet parent) { + this.p = parent; + this.g = (ARGraphics)p.g; + setEventHandler(); + } + + public void addImage(String name, PImage img) { + addImageImp(name, img, null); + } + + public void addImage(String name, PImage img, float size) { + addImageImp(name, img, size); + } + + private void addImageImp(String name, PImage img, Float size) { + if (db == null) { + // Creating a new database of augmented images. + db = new AugmentedImageDatabase(g.surfar.session); + } + + Bitmap bitmap = (Bitmap)img.getNative(); + if (size != null) { + db.addImage(name, bitmap, size); + } else { + db.addImage(name, bitmap); + } + + // Reset the session config with the updated image database + Config config = new Config(g.surfar.session); + config.setAugmentedImageDatabase(db); + g.surfar.session.configure(config); + } + + public void start() { + cleanup(); + g.addTracker(this); + } + + public void stop() { + g.removeTracker(this); + } + + public int count() { + return g.trackableCount(); + } + + public ARTrackable get(int idx) { + int id = g.trackableId(idx); + String name = g.trackableName(idx); + String sid = String.valueOf(id); + if (!trackables.containsKey(sid)) { + ARTrackable t = new ARTrackable(g, id, name); + trackables.put(sid, t); + } + return get(sid); + } + + public ARTrackable get(String id) { + return trackables.get(id); + } + + public ARTrackable get(int mx, int my) { + HitResult hit = g.getHitResult(mx, my); + if (hit != null) { + int idx = g.getTrackable(hit); + ARTrackable t = get(idx); + t.hit = hit; + return t; + } else { + return null; + } + } + + protected void create(int idx) { + if (trackableEventMethod != null) { + try { + ARTrackable t = get(idx); + trackableEventMethod.invoke(p, t); + } catch (Exception e) { + System.err.println("error, disabling trackableEventMethod() for AR tracker"); + e.printStackTrace(); + trackableEventMethod = null; + } + } + } + + public void clearAnchors(Collection anchors) { + for (ARAnchor anchor : anchors) { + if (anchor.isStopped() || anchor.isDisposed()) { + anchor.dispose(); + toRemove.add(anchor); + } + } + anchors.removeAll(toRemove); + toRemove.clear(); + } + + protected void cleanup() { + // Remove any inactive trackables left over in the tracker. + Set ids = trackables.keySet(); + for (String id: ids) { + ARTrackable t = trackables.get(id); + if (t.isStopped()) trackables.remove(id); + } + } + + protected void remove(int idx) { + int id = g.trackableId(idx); + String sid = String.valueOf(id); + remove(sid); + } + + protected void remove(String id) { + trackables.remove(id); + } + + protected void setEventHandler() { + try { + trackableEventMethod = p.getClass().getMethod("trackableEvent", ARTrackable.class); + return; + } catch (Exception e) { + // no such method, or an error... which is fine, just ignore + } + + // trackableEvent can alternatively be defined as receiving an Object, to allow + // Processing mode implementors to support the video library without linking + // to it at build-time. + try { + trackableEventMethod = p.getClass().getMethod("trackableEvent", Object.class); + } catch (Exception e) { + // no such method, or an error... which is fine, just ignore + } + } +} diff --git a/libs/processing-ar/src/main/java/processing/ar/BackgroundRenderer.java b/libs/processing-ar/src/main/java/processing/ar/BackgroundRenderer.java new file mode 100644 index 000000000..f00464866 --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/BackgroundRenderer.java @@ -0,0 +1,152 @@ +package processing.ar; + +import android.content.Context; +import android.opengl.GLES11Ext; +import android.opengl.GLES20; + +import com.google.ar.core.Frame; + +import java.net.URL; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public class BackgroundRenderer { + + private static final int COORDS_PER_VERTEX = 3; + private static final int TEXCOORDS_PER_VERTEX = 2; + private static final int FLOAT_SIZE = 4; + + private FloatBuffer quadVertices; + private FloatBuffer quadTexCoord; + private FloatBuffer quadTexCoordTransformed; + + private int quadProgram; + + private int quadPositionParam; + private int quadTexCoordParam; + private int textureId = -1; + + static private URL screenquad_vertex = + BackgroundRenderer.class.getResource("/assets/shaders/BackgroundVert.glsl"); + static private URL screenquad_fragment = + BackgroundRenderer.class.getResource("/assets/shaders/BackgroundFrag.glsl"); + + private String VERTICES_ERROR = "Unexpected number of vertices in BackgroundRenderer"; + private String ERROR_TAG = "Error"; + private String CREATION_ERROR = "Program creation"; + private String PARAMETERS_ERROR = "Program parameters"; + private String DRAW_ERROR = "Draw"; + + public BackgroundRenderer(Context context) { + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + textureId = textures[0]; + int textureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; + GLES20.glBindTexture(textureTarget, textureId); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + + int numVertices = 4; + if (numVertices != QUAD_COORDS.length / COORDS_PER_VERTEX) { + throw new RuntimeException(VERTICES_ERROR); + } + + ByteBuffer bbVertices = ByteBuffer.allocateDirect(QUAD_COORDS.length * FLOAT_SIZE); + bbVertices.order(ByteOrder.nativeOrder()); + quadVertices = bbVertices.asFloatBuffer(); + quadVertices.put(QUAD_COORDS); + quadVertices.position(0); + + ByteBuffer bbTexCoords = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoords.order(ByteOrder.nativeOrder()); + quadTexCoord = bbTexCoords.asFloatBuffer(); + quadTexCoord.put(QUAD_TEXCOORDS); + quadTexCoord.position(0); + + ByteBuffer bbTexCoordsTransformed = + ByteBuffer.allocateDirect(numVertices * TEXCOORDS_PER_VERTEX * FLOAT_SIZE); + bbTexCoordsTransformed.order(ByteOrder.nativeOrder()); + quadTexCoordTransformed = bbTexCoordsTransformed.asFloatBuffer(); + + int vertexShader = + ShaderUtils.loadGLShader(ERROR_TAG, context, GLES20.GL_VERTEX_SHADER, screenquad_vertex); + int fragmentShader = + ShaderUtils.loadGLShader( + ERROR_TAG, context, GLES20.GL_FRAGMENT_SHADER, screenquad_fragment); + + quadProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(quadProgram, vertexShader); + GLES20.glAttachShader(quadProgram, fragmentShader); + GLES20.glLinkProgram(quadProgram); + GLES20.glUseProgram(quadProgram); + + ShaderUtils.checkGLError(ERROR_TAG, CREATION_ERROR); + + quadPositionParam = GLES20.glGetAttribLocation(quadProgram, "a_Position"); + quadTexCoordParam = GLES20.glGetAttribLocation(quadProgram, "a_TexCoord"); + + ShaderUtils.checkGLError(ERROR_TAG, PARAMETERS_ERROR); + } + + public int getTextureId() { + return textureId; + } + + public void draw(Frame frame) { + + if (frame.hasDisplayGeometryChanged()) { + frame.transformDisplayUvCoords(quadTexCoord, quadTexCoordTransformed); + } + + GLES20.glDisable(GLES20.GL_DEPTH_TEST); + GLES20.glDepthMask(false); + + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId); + + GLES20.glUseProgram(quadProgram); + + GLES20.glVertexAttribPointer( + quadPositionParam, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, 0, quadVertices); + + GLES20.glVertexAttribPointer( + quadTexCoordParam, + TEXCOORDS_PER_VERTEX, + GLES20.GL_FLOAT, + false, + 0, + quadTexCoordTransformed); + + GLES20.glEnableVertexAttribArray(quadPositionParam); + GLES20.glEnableVertexAttribArray(quadTexCoordParam); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + GLES20.glDisableVertexAttribArray(quadPositionParam); + GLES20.glDisableVertexAttribArray(quadTexCoordParam); + + GLES20.glDepthMask(true); + GLES20.glEnable(GLES20.GL_DEPTH_TEST); + + ShaderUtils.checkGLError(ERROR_TAG, DRAW_ERROR); + } + + private static final float[] QUAD_COORDS = + new float[]{ + -1.0f, -1.0f, 0.0f, + -1.0f, +1.0f, 0.0f, + +1.0f, -1.0f, 0.0f, + +1.0f, +1.0f, 0.0f, + }; + + private static final float[] QUAD_TEXCOORDS = + new float[]{ + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f, + }; +} diff --git a/libs/processing-ar/src/main/java/processing/ar/RotationHandler.java b/libs/processing-ar/src/main/java/processing/ar/RotationHandler.java new file mode 100644 index 000000000..e11c4b8e3 --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/RotationHandler.java @@ -0,0 +1,63 @@ +package processing.ar; + + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.view.Display; +import android.view.WindowManager; + +import com.google.ar.core.Session; + + +public class RotationHandler implements DisplayManager.DisplayListener { + private boolean viewportChanged; + private int viewportWidth; + private int viewportHeight; + private final Context context; + private final Display display; + + public RotationHandler(Context context) { + this.context = context; + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + display = windowManager.getDefaultDisplay(); + } + + public void onResume() { + ((DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE)).registerDisplayListener(this, null); + } + + public void onPause() { + ((DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE)).unregisterDisplayListener(this); + } + + public void onSurfaceChanged(int width, int height) { + viewportWidth = width; + viewportHeight = height; + viewportChanged = true; + } + + public void updateSessionIfNeeded(Session session) { + if (viewportChanged) { + int displayRotation = display.getRotation(); + session.setDisplayGeometry(displayRotation, viewportWidth, viewportHeight); + viewportChanged = false; + } + } + + public int getRotation() { + return display.getRotation(); + } + + @Override + public void onDisplayAdded(int displayId) { + } + + @Override + public void onDisplayRemoved(int displayId) { + } + + @Override + public void onDisplayChanged(int displayId) { + viewportChanged = true; + } +} \ No newline at end of file diff --git a/libs/processing-ar/src/main/java/processing/ar/ShaderUtils.java b/libs/processing-ar/src/main/java/processing/ar/ShaderUtils.java new file mode 100644 index 000000000..97209d51b --- /dev/null +++ b/libs/processing-ar/src/main/java/processing/ar/ShaderUtils.java @@ -0,0 +1,60 @@ +package processing.ar; + +import android.content.Context; +import android.opengl.GLES20; + +import processing.core.PApplet; +import processing.core.PGraphics; + +import java.io.IOException; +import java.net.URL; + +public class ShaderUtils { + public static int loadGLShader(String tag, Context context, int type, URL resUrl) { + String code = readRawTextFile(resUrl); + int shader = GLES20.glCreateShader(type); + GLES20.glShaderSource(shader, code); + GLES20.glCompileShader(shader); + + final int[] compileStatus = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); + + if (compileStatus[0] == 0) { + PGraphics.showWarning("Error compiling shader: " + GLES20.glGetShaderInfoLog(shader)); + GLES20.glDeleteShader(shader); + shader = 0; + } + + if (shader == 0) { + throw new RuntimeException("Error creating shader."); + } + + return shader; + } + + public static void checkGLError(String tag, String label) { + int lastError = GLES20.GL_NO_ERROR; + int error; + while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + PGraphics.showWarning(label + ": glError " + error); + lastError = error; + } + if (lastError != GLES20.GL_NO_ERROR) { + throw new RuntimeException(label + ": glError " + lastError); + } + } + + private static String readRawTextFile(URL url) { + try { + String[] sample = PApplet.loadStrings(url.openStream()); + StringBuilder stringBuilder = new StringBuilder(); + for (String sam : sample) { + stringBuilder.append(sam).append("\n"); + } + return stringBuilder.toString(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/libs/processing-core/build.gradle b/libs/processing-core/build.gradle new file mode 100644 index 000000000..b7952ec4d --- /dev/null +++ b/libs/processing-core/build.gradle @@ -0,0 +1,33 @@ +plugins { + id 'com.android.library' +} + +android { + + namespace "processing.core" + + defaultConfig { + minSdkVersion 17 + targetSdkVersion 33 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + testImplementation 'junit:junit:4.13.2' + + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.android.support:wearable:2.9.0' + compileOnly 'com.google.android.wearable:wearable:2.9.0' +} \ No newline at end of file diff --git a/libs/processing-core/proguard-rules.pro b/libs/processing-core/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/libs/processing-core/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/libs/processing-core/src/main/AndroidManifest.xml b/libs/processing-core/src/main/AndroidManifest.xml new file mode 100755 index 000000000..97330b776 --- /dev/null +++ b/libs/processing-core/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/core/src/processing/opengl/ColorFrag.glsl b/libs/processing-core/src/main/assets/shaders/ColorFrag.glsl similarity index 77% rename from core/src/processing/opengl/ColorFrag.glsl rename to libs/processing-core/src/main/assets/shaders/ColorFrag.glsl index d5d23ae22..5f86d92b4 100644 --- a/core/src/processing/opengl/ColorFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/ColorFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,7 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ #ifdef GL_ES precision mediump float; diff --git a/core/src/processing/opengl/ColorVert.glsl b/libs/processing-core/src/main/assets/shaders/ColorVert.glsl similarity index 78% rename from core/src/processing/opengl/ColorVert.glsl rename to libs/processing-core/src/main/assets/shaders/ColorVert.glsl index 3736ffd2e..65fd55716 100644 --- a/core/src/processing/opengl/ColorVert.glsl +++ b/libs/processing-core/src/main/assets/shaders/ColorVert.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define PROCESSING_COLOR_SHADER +*/ uniform mat4 transformMatrix; diff --git a/core/src/processing/opengl/LightFrag.glsl b/libs/processing-core/src/main/assets/shaders/LightFrag.glsl similarity index 78% rename from core/src/processing/opengl/LightFrag.glsl rename to libs/processing-core/src/main/assets/shaders/LightFrag.glsl index b566c8e5b..6b60d4039 100644 --- a/core/src/processing/opengl/LightFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/LightFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,8 +18,8 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - +*/ + #ifdef GL_ES precision mediump float; precision mediump int; diff --git a/core/src/processing/opengl/LightVert.glsl b/libs/processing-core/src/main/assets/shaders/LightVert.glsl similarity index 94% rename from core/src/processing/opengl/LightVert.glsl rename to libs/processing-core/src/main/assets/shaders/LightVert.glsl index 8d75f970a..adc4bf9ad 100644 --- a/core/src/processing/opengl/LightVert.glsl +++ b/libs/processing-core/src/main/assets/shaders/LightVert.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define PROCESSING_LIGHT_SHADER +*/ uniform mat4 modelviewMatrix; uniform mat4 transformMatrix; @@ -98,7 +98,7 @@ void main() { if (lightCount == i) break; vec3 lightPos = lightPosition[i].xyz; - bool isDir = zero_float < lightPosition[i].w; + bool isDir = lightPosition[i].w < one_float; float spotCos = lightSpot[i].x; float spotExp = lightSpot[i].y; diff --git a/core/src/processing/opengl/LineFrag.glsl b/libs/processing-core/src/main/assets/shaders/LineFrag.glsl similarity index 77% rename from core/src/processing/opengl/LineFrag.glsl rename to libs/processing-core/src/main/assets/shaders/LineFrag.glsl index dc08a3fa2..9046e17e9 100644 --- a/core/src/processing/opengl/LineFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/LineFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,7 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ #ifdef GL_ES precision mediump float; diff --git a/libs/processing-core/src/main/assets/shaders/LineVert.glsl b/libs/processing-core/src/main/assets/shaders/LineVert.glsl new file mode 100644 index 000000000..15e7bf951 --- /dev/null +++ b/libs/processing-core/src/main/assets/shaders/LineVert.glsl @@ -0,0 +1,99 @@ +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-17 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +#define PROCESSING_LINE_SHADER + +uniform mat4 modelviewMatrix; +uniform mat4 projectionMatrix; + +uniform vec4 viewport; +uniform int perspective; +uniform vec3 scale; + +attribute vec4 position; +attribute vec4 color; +attribute vec4 direction; + +varying vec4 vertColor; + +void main() { + vec4 posp = modelviewMatrix * position; + vec4 posq = modelviewMatrix * (position + vec4(direction.xyz, 0)); + + // Moving vertices slightly toward the camera + // to avoid depth-fighting with the fill triangles. + // Discussed here: + // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=252848 + posp.xyz = posp.xyz * scale; + posq.xyz = posq.xyz * scale; + + vec4 p = projectionMatrix * posp; + vec4 q = projectionMatrix * posq; + + // formula to convert from clip space (range -1..1) to screen space (range 0..[width or height]) + // screen_p = (p.xy/p.w + <1,1>) * 0.5 * viewport.zw + + // prevent division by W by transforming the tangent formula (div by 0 causes + // the line to disappear, see https://github.com/processing/processing/issues/5183) + // t = screen_q - screen_p + // + // tangent is normalized and we don't care which direction it points to (+-) + // t = +- normalize( screen_q - screen_p ) + // t = +- normalize( (q.xy/q.w+<1,1>)*0.5*viewport.zw - (p.xy/p.w+<1,1>)*0.5*viewport.zw ) + // + // extract common factor, <1,1> - <1,1> cancels out + // t = +- normalize( (q.xy/q.w - p.xy/p.w) * 0.5 * viewport.zw ) + // + // convert to common divisor + // t = +- normalize( ((q.xy*p.w - p.xy*q.w) / (p.w*q.w)) * 0.5 * viewport.zw ) + // + // remove the common scalar divisor/factor, not needed due to normalize and +- + // (keep viewport - can't remove because it has different components for x and y + // and corrects for aspect ratio, see https://github.com/processing/processing/issues/5181) + // t = +- normalize( (q.xy*p.w - p.xy*q.w) * viewport.zw ) + + vec2 tangent = (q.xy*p.w - p.xy*q.w) * viewport.zw; + // don't normalize zero vector (line join triangles and lines perpendicular to the eye plane) + tangent = length(tangent) == 0.0 ? vec2(0.0, 0.0) : normalize(tangent); + + // flip tangent to normal (it's already normalized) + vec2 normal = vec2(-tangent.y, tangent.x); + + float thickness = direction.w; + vec2 offset = normal * thickness; + + // Perspective --- + // convert from world to clip by multiplying with projection scaling factor + // to get the right thickness (see https://github.com/processing/processing/issues/5182) + // invert Y, projections in Processing invert Y + vec2 perspScale = (projectionMatrix * vec4(1, -1, 0, 0)).xy; + + // No Perspective --- + // multiply by W (to cancel out division by W later in the pipeline) and + // convert from screen to clip (derived from clip to screen above) + vec2 noPerspScale = p.w / (0.5 * viewport.zw); + + gl_Position.xy = p.xy + offset.xy * mix(noPerspScale, perspScale, float(perspective > 0)); + gl_Position.zw = p.zw; + + vertColor = color; +} diff --git a/core/src/processing/opengl/MaskFrag.glsl b/libs/processing-core/src/main/assets/shaders/MaskFrag.glsl similarity index 82% rename from core/src/processing/opengl/MaskFrag.glsl rename to libs/processing-core/src/main/assets/shaders/MaskFrag.glsl index 508a39649..3d7e7dbf1 100644 --- a/core/src/processing/opengl/MaskFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/MaskFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,7 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ #ifdef GL_ES precision mediump float; diff --git a/libs/processing-core/src/main/assets/shaders/P2DFrag.glsl b/libs/processing-core/src/main/assets/shaders/P2DFrag.glsl new file mode 100644 index 000000000..ce3a3fb27 --- /dev/null +++ b/libs/processing-core/src/main/assets/shaders/P2DFrag.glsl @@ -0,0 +1,14 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +varying vec4 vertColor; +varying vec2 vertTexCoord; +varying float vertTexFactor; + +uniform sampler2D texture; + +void main() { + gl_FragColor = mix(vertColor, vertColor * texture2D(texture, vertTexCoord), vertTexFactor); +} diff --git a/libs/processing-core/src/main/assets/shaders/P2DVert.glsl b/libs/processing-core/src/main/assets/shaders/P2DVert.glsl new file mode 100644 index 000000000..4e9a1f7e1 --- /dev/null +++ b/libs/processing-core/src/main/assets/shaders/P2DVert.glsl @@ -0,0 +1,23 @@ +attribute vec3 position; +attribute vec4 color; +attribute vec2 texCoord; +attribute float texFactor; + +varying vec4 vertColor; +varying vec2 vertTexCoord; +varying float vertTexFactor; + +uniform mat4 transform; +uniform vec2 texScale; + +void main() { + gl_Position = transform * vec4(position, 1); + + //we avoid affecting the Z component by the transform + //because it would mess up our depth testing + gl_Position.z = position.z; + + vertColor = color.zyxw; + vertTexCoord = texCoord * texScale; + vertTexFactor = texFactor; +} diff --git a/core/src/processing/opengl/PointFrag.glsl b/libs/processing-core/src/main/assets/shaders/PointFrag.glsl similarity index 77% rename from core/src/processing/opengl/PointFrag.glsl rename to libs/processing-core/src/main/assets/shaders/PointFrag.glsl index d5d23ae22..5f86d92b4 100644 --- a/core/src/processing/opengl/PointFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/PointFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,7 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ #ifdef GL_ES precision mediump float; diff --git a/core/src/processing/opengl/PointVert.glsl b/libs/processing-core/src/main/assets/shaders/PointVert.glsl similarity index 51% rename from core/src/processing/opengl/PointVert.glsl rename to libs/processing-core/src/main/assets/shaders/PointVert.glsl index c677ce75b..292674615 100644 --- a/core/src/processing/opengl/PointVert.glsl +++ b/libs/processing-core/src/main/assets/shaders/PointVert.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-17 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define PROCESSING_POINT_SHADER +*/ uniform mat4 projectionMatrix; uniform mat4 modelviewMatrix; @@ -32,24 +32,25 @@ attribute vec2 offset; varying vec4 vertColor; -vec4 windowToClipVector(vec2 window, vec4 viewport, float clipw) { - vec2 xypos = (window / viewport.zw) * 2.0; - return vec4(xypos, 0.0, 0.0) * clipw; -} - void main() { vec4 pos = modelviewMatrix * position; vec4 clip = projectionMatrix * pos; - - if (0 < perspective) { - // Perspective correction (points will look thiner as they move away - // from the view position). - gl_Position = clip + projectionMatrix * vec4(offset.xy, 0, 0); - } else { - // No perspective correction. - vec4 offset = windowToClipVector(offset.xy, viewport, clip.w); - gl_Position = clip + offset; - } + + // Perspective --- + // convert from world to clip by multiplying with projection scaling factor + // invert Y, projections in Processing invert Y + vec2 perspScale = (projectionMatrix * vec4(1, -1, 0, 0)).xy; + + // formula to convert from clip space (range -1..1) to screen space (range 0..[width or height]) + // screen_p = (p.xy/p.w + <1,1>) * 0.5 * viewport.zw + + // No Perspective --- + // multiply by W (to cancel out division by W later in the pipeline) and + // convert from screen to clip (derived from clip to screen above) + vec2 noPerspScale = clip.w / (0.5 * viewport.zw); + + gl_Position.xy = clip.xy + offset.xy * mix(noPerspScale, perspScale, float(perspective > 0)); + gl_Position.zw = clip.zw; vertColor = color; } \ No newline at end of file diff --git a/core/src/processing/opengl/TextureFrag.glsl b/libs/processing-core/src/main/assets/shaders/TexFrag.glsl similarity index 79% rename from core/src/processing/opengl/TextureFrag.glsl rename to libs/processing-core/src/main/assets/shaders/TexFrag.glsl index f041e61d1..c8e183d49 100644 --- a/core/src/processing/opengl/TextureFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/TexFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,7 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ #ifdef GL_ES precision mediump float; diff --git a/core/src/processing/opengl/TexlightFrag.glsl b/libs/processing-core/src/main/assets/shaders/TexLightFrag.glsl similarity index 80% rename from core/src/processing/opengl/TexlightFrag.glsl rename to libs/processing-core/src/main/assets/shaders/TexLightFrag.glsl index f423e49d3..fd9b61576 100644 --- a/core/src/processing/opengl/TexlightFrag.glsl +++ b/libs/processing-core/src/main/assets/shaders/TexLightFrag.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,8 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - +*/ #ifdef GL_ES precision mediump float; precision mediump int; diff --git a/core/src/processing/opengl/TexlightVert.glsl b/libs/processing-core/src/main/assets/shaders/TexLightVert.glsl similarity index 94% rename from core/src/processing/opengl/TexlightVert.glsl rename to libs/processing-core/src/main/assets/shaders/TexLightVert.glsl index d9f2cde3a..57551edce 100644 --- a/core/src/processing/opengl/TexlightVert.glsl +++ b/libs/processing-core/src/main/assets/shaders/TexLightVert.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define PROCESSING_TEXLIGHT_SHADER +*/ uniform mat4 modelviewMatrix; uniform mat4 transformMatrix; @@ -101,7 +101,7 @@ void main() { if (lightCount == i) break; vec3 lightPos = lightPosition[i].xyz; - bool isDir = zero_float < lightPosition[i].w; + bool isDir = lightPosition[i].w < one_float; float spotCos = lightSpot[i].x; float spotExp = lightSpot[i].y; diff --git a/core/src/processing/opengl/TextureVert.glsl b/libs/processing-core/src/main/assets/shaders/TexVert.glsl similarity index 80% rename from core/src/processing/opengl/TextureVert.glsl rename to libs/processing-core/src/main/assets/shaders/TexVert.glsl index 87f101536..c07dc30d7 100644 --- a/core/src/processing/opengl/TextureVert.glsl +++ b/libs/processing-core/src/main/assets/shaders/TexVert.glsl @@ -1,11 +1,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-16 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -16,9 +18,7 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#define PROCESSING_TEXTURE_SHADER +*/ uniform mat4 transformMatrix; uniform mat4 texMatrix; diff --git a/core/src/processing/core/PGraphicsAndroid2D.java b/libs/processing-core/src/main/java/processing/a2d/PGraphicsAndroid2D.java similarity index 76% rename from core/src/processing/core/PGraphicsAndroid2D.java rename to libs/processing-core/src/main/java/processing/a2d/PGraphicsAndroid2D.java index 2916846c0..b82ced644 100644 --- a/core/src/processing/core/PGraphicsAndroid2D.java +++ b/libs/processing-core/src/main/java/processing/a2d/PGraphicsAndroid2D.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2005-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2005-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -20,18 +21,42 @@ Boston, MA 02111-1307 USA */ -package processing.core; +package processing.a2d; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; - -import processing.data.XML; +import android.annotation.SuppressLint; +import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo; +import android.content.Context; import android.graphics.*; import android.graphics.Bitmap.Config; import android.graphics.Paint.Style; +import android.os.Build; +import android.os.Environment; +import android.view.SurfaceHolder; +import static android.os.Environment.isExternalStorageRemovable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.ByteBuffer; +import java.io.InputStream; +import java.util.zip.GZIPInputStream; +import processing.android.AppComponent; +import processing.core.PApplet; +import processing.core.PFont; +import processing.core.PGraphics; +import processing.core.PImage; +import processing.core.PMatrix; +import processing.core.PMatrix2D; +import processing.core.PMatrix3D; +import processing.core.PShape; +import processing.core.PShapeSVG; +import processing.core.PSurface; +import processing.data.XML; /** * Subclass for PGraphics that implements the graphics API using @@ -39,6 +64,7 @@ * with the original (desktop) version of Processing. */ public class PGraphicsAndroid2D extends PGraphics { + static public boolean useBitmap = true; public Canvas canvas; // like g2 for PGraphicsJava2D @@ -51,9 +77,12 @@ public class PGraphicsAndroid2D extends PGraphics { float[] curveDrawX; float[] curveDrawY; -// int transformCount; -// Matrix[] transformStack; - float[] transform; + static protected final int MATRIX_STACK_DEPTH = 32; + protected float[][] transformStack; + public PMatrix2D transform; + protected Matrix transformMatrix; + protected float[] transformArray; + int transformCount; // Line2D.Float line = new Line2D.Float(); // Ellipse2D.Float ellipse = new Ellipse2D.Float(); @@ -86,15 +115,27 @@ public class PGraphicsAndroid2D extends PGraphics { Paint tintPaint; + /** + * Marks when changes to the size have occurred, so that the backing bitmap + * can be recreated. + */ + protected boolean sized; + + /** + * Marks when some changes have occurred, to the surface view. + */ + protected boolean changed; + ////////////////////////////////////////////////////////////// // INTERNAL public PGraphicsAndroid2D() { -// transformStack = new Matrix[MATRIX_STACK_DEPTH]; -// transform = new float[6]; - transform = new float[9]; + transformStack = new float[MATRIX_STACK_DEPTH][6]; + transform = new PMatrix2D(); + transformMatrix = new Matrix(); + transformArray = new float[9]; path = new Path(); rect = new RectF(); @@ -115,74 +156,57 @@ public PGraphicsAndroid2D() { //public void setPath(String path) - - /** - * Called in response to a resize event, handles setting the - * new width and height internally, as well as re-allocating - * the pixel buffer for the new size. - * - * Note that this will nuke any cameraMode() settings. - */ @Override - public void setSize(int iwidth, int iheight) { // ignore - width = iwidth; - height = iheight; - width1 = width - 1; - height1 = height - 1; - - allocate(); - reapplySettings(); + public void surfaceChanged() { + changed = true; } @Override - protected void allocate() { - if (bitmap != null) bitmap.recycle(); - bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); - canvas = new Canvas(bitmap); -// image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); -// canvas = (Graphics2D) image.getGraphics(); + public void setSize(int iwidth, int iheight) { + sized = iwidth != width || iheight != height; + super.setSize(iwidth, iheight); } @Override public void dispose() { - bitmap.recycle(); + if (bitmap != null) bitmap.recycle(); } + @Override + public PSurface createSurface(AppComponent component, SurfaceHolder holder, boolean reset) { // ignore + return new PSurfaceAndroid2D(this, component, holder); + } + ////////////////////////////////////////////////////////////// // FRAME - /* - public void requestDraw() { - parent.surfaceView.requestRender(); - } - */ - -// public boolean canDraw() { -// return true; -// } - - - @Override - public void requestDraw() { - parent.handleDraw(); + @SuppressLint("NewApi") + protected Canvas checkCanvas() { + if ((canvas == null || sized) && (useBitmap || !primaryGraphics)) { + if (bitmap == null || bitmap.getWidth() * bitmap.getHeight() < width * height || + Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + if (bitmap != null) bitmap.recycle(); + bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); + } else { + // reconfigure is only available in API level 19 or higher. + bitmap.reconfigure(width, height, bitmap.getConfig()); + } + canvas = new Canvas(bitmap); + sized = false; + } + restoreSurface(); + return canvas; } @Override public void beginDraw() { -// if (primarySurface) { -// canvas = parent.getSurfaceHolder().lockCanvas(null); -// if (canvas == null) { -// throw new RuntimeException("canvas is still null"); -// } -// } else { -// throw new RuntimeException("not primary surface"); -// } + canvas = checkCanvas(); checkSettings(); @@ -195,26 +219,27 @@ public void beginDraw() { @Override public void endDraw() { - // hm, mark pixels as changed, because this will instantly do a full - // copy of all the pixels to the surface.. so that's kind of a mess. - //updatePixels(); - -// if (primarySurface) { -// if (canvas != null) { -// parent.getSurfaceHolder().unlockCanvasAndPost(canvas); -// } -// } - - if (primarySurface) { - Canvas screen = null; - try { - screen = parent.getSurfaceHolder().lockCanvas(null); - if (screen != null) { - screen.drawBitmap(bitmap, new Matrix(), null); - } - } finally { - if (screen != null) { - parent.getSurfaceHolder().unlockCanvasAndPost(screen); + if (bitmap == null) return; + + if (primaryGraphics) { + SurfaceHolder holder = parent.getSurface().getSurfaceHolder(); + if (holder != null) { + Canvas screen = null; + try { + screen = holder.lockCanvas(null); + if (screen != null) { + screen.drawBitmap(bitmap, new Matrix(), null); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (screen != null) { + try { + holder.unlockCanvasAndPost(screen); + } catch (IllegalStateException ex) { + } catch (IllegalArgumentException ex) { + } + } } } } else { @@ -349,6 +374,14 @@ public void vertex(float x, float y) { } break; + case LINE_STRIP: + case LINE_LOOP: + if (vertexCount >= 2) { + line(vertices[vertexCount-2][X], + vertices[vertexCount-2][Y], x, y); + } + break; + case TRIANGLES: if ((vertexCount % 3) == 0) { triangle(vertices[vertexCount - 3][X], @@ -420,6 +453,12 @@ public void vertex(float x, float y, float z) { } + @Override + public void vertex(float[] v) { + vertex(v[X], v[Y]); + } + + @Override public void vertex(float x, float y, float u, float v) { showVariationWarning("vertex(x, y, u, v)"); @@ -441,7 +480,7 @@ public void breakShape() { @Override public void endShape(int mode) { if (shape == POINTS && stroke && vertexCount > 0) { - Matrix m = canvas.getMatrix(); + Matrix m = getMatrixImp(); if (strokeWeight == 1 && m.isIdentity()) { if (screenPoint == null) { screenPoint = new float[2]; @@ -474,11 +513,32 @@ public void endShape(int mode) { } drawPath(); } + } else if (shape == LINE_LOOP && vertexCount >= 2) { + line(vertices[vertexCount-1][X], + vertices[vertexCount-1][Y], + vertices[0][X], + vertices[0][Y]); } shape = 0; } + ////////////////////////////////////////////////////////////// + + // CLIPPING + + + @Override + protected void clipImpl(float x1, float y1, float x2, float y2) { + canvas.clipRect(x1, y1, x2, y2); + } + + + @Override + public void noClip() { + canvas.clipRect(0, 0, width, height, Region.Op.REPLACE); + } + ////////////////////////////////////////////////////////////// @@ -754,14 +814,43 @@ protected void arcImpl(float x, float y, float w, float h, } } else if (mode == OPEN) { if (fill) { - showMissingWarning("arc"); + // Android does not support stroke and fill with different color + // after drawing the arc,draw the arc with Paint.Style.Stroke style + // again + canvas.drawArc(rect, start, sweep, false, fillPaint); + canvas.drawArc(rect, start, sweep, false, strokePaint); } if (stroke) { canvas.drawArc(rect, start, sweep, false, strokePaint); } } else if (mode == CHORD) { - showMissingWarning("arc"); + // Draw an extra line between start angle point and end point to + // achieve the chord + float endAngle = start + sweep; + float halfRectWidth = rect.width()/2; + float halfRectHeight = rect.height()/2; + float centerX = rect.centerX(); + float centerY = rect.centerY(); + + float startX = (float) (halfRectWidth* Math.cos(Math.toRadians(start))) + centerX; + float startY = (float) (halfRectHeight * Math.sin(Math.toRadians(start))) + centerY; + float endX = (float) (halfRectWidth * Math.cos(Math.toRadians(endAngle))) + centerX; + float endY = (float) (halfRectHeight * Math.sin(Math.toRadians(endAngle))) + centerY; + if (fill) { + // draw the fill arc + canvas.drawArc(rect,start,sweep,false,fillPaint); + // draw the arc round border + canvas.drawArc(rect,start,sweep,false,strokePaint); + // draw the straight border + canvas.drawLine(startX,startY,endX,endY,strokePaint); + } + if (stroke) { + // draw the arc + canvas.drawArc(rect,start,sweep,false,strokePaint); + // draw the straight border + canvas.drawLine(startX,startY,endX,endY,strokePaint); + } } else if (mode == PIE) { if (fill) { canvas.drawArc(rect, start, sweep, true, fillPaint); @@ -769,7 +858,6 @@ protected void arcImpl(float x, float y, float w, float h, if (stroke) { canvas.drawArc(rect, start, sweep, true, strokePaint); } - } } } @@ -945,8 +1033,9 @@ public void curveDetail(int detail) { @Override - public void smooth() { - smooth = true; + public void smooth(int quality) { // ignore + super.smooth(quality); +// smooth = true; // canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_ON); // canvas.setRenderingHint(RenderingHints.KEY_INTERPOLATION, @@ -958,7 +1047,8 @@ public void smooth() { @Override public void noSmooth() { - smooth = false; + super.noSmooth(); +// smooth = false; // canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_OFF); // canvas.setRenderingHint(RenderingHints.KEY_INTERPOLATION, @@ -999,20 +1089,23 @@ public void noSmooth() { protected void imageImpl(PImage src, float x1, float y1, float x2, float y2, int u1, int v1, int u2, int v2) { - if (src.bitmap != null && src.bitmap.isRecycled()) { + Bitmap bitmap = (Bitmap)src.getNative(); + + if (bitmap != null && bitmap.isRecycled()) { // Let's make sure it is recreated - src.bitmap = null; + bitmap = null; } - if (src.bitmap == null && src.format == ALPHA) { + if (bitmap == null && src.format == ALPHA) { // create an alpha bitmap for this feller - src.bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); int[] px = new int[src.pixels.length]; for (int i = 0; i < px.length; i++) { px[i] = src.pixels[i] << 24 | 0xFFFFFF; } - src.bitmap.setPixels(px, 0, src.width, 0, 0, src.width, src.height); - src.modified = false; + bitmap.setPixels(px, 0, src.width, 0, 0, src.width, src.height); + modified = false; + src.setNative(bitmap); } // this version's not usable because it doesn't allow you to set output w/h @@ -1023,23 +1116,26 @@ protected void imageImpl(PImage src, // src.format == ARGB, tint ? tintPaint : null); // } else { - if (src.bitmap == null || - src.width != src.bitmap.getWidth() || - src.height != src.bitmap.getHeight()) { - if (src.bitmap != null) src.bitmap.recycle(); - src.bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); - src.modified = true; + if (bitmap == null || + src.width != bitmap.getWidth() || + src.height != bitmap.getHeight()) { + if (bitmap != null) bitmap.recycle(); + bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + modified = true; + src.setNative(bitmap); } - if (src.modified) { + + if (src.isModified()) { //System.out.println("mutable, recycled = " + who.bitmap.isMutable() + ", " + who.bitmap.isRecycled()); - if (!src.bitmap.isMutable()) { - src.bitmap.recycle(); - src.bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + if (!bitmap.isMutable()) { + bitmap.recycle(); + bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + src.setNative(bitmap); } if (src.pixels != null) { - src.bitmap.setPixels(src.pixels, 0, src.width, 0, 0, src.width, src.height); + bitmap.setPixels(src.pixels, 0, src.width, 0, 0, src.width, src.height); } - src.modified = false; + src.setModified(false); } if (imageImplSrcRect == null) { @@ -1053,17 +1149,19 @@ protected void imageImpl(PImage src, //System.out.println(PApplet.hex(fillPaint.getColor())); //canvas.drawBitmap(who.bitmap, imageImplSrcRect, imageImplDstRect, fillPaint); // System.out.println("drawing lower, tint = " + tint + " " + PApplet.hex(tintPaint.getColor())); - canvas.drawBitmap(src.bitmap, imageImplSrcRect, imageImplDstRect, tint ? tintPaint : null); + canvas.drawBitmap(bitmap, imageImplSrcRect, imageImplDstRect, tint ? tintPaint : null); // If the OS things the memory is low, then recycles bitmaps automatically... // but I don't think it is particularly efficient, as the bitmaps are stored // in native heap for Android 10 and older. MemoryInfo mi = new MemoryInfo(); - ActivityManager activityManager = (ActivityManager) parent.getApplicationContext().getSystemService(android.content.Context.ACTIVITY_SERVICE); + Activity activity = parent.getSurface().getActivity(); + if (activity == null) return; + ActivityManager activityManager = (ActivityManager) activity.getSystemService(android.content.Context.ACTIVITY_SERVICE); activityManager.getMemoryInfo(mi); if (mi.lowMemory) { - src.bitmap.recycle(); - src.bitmap = null; + bitmap.recycle(); + src.setNative(null); } } @@ -1157,10 +1255,16 @@ public PShape loadShape(String filename) { public void textFont(PFont which) { super.textFont(which); fillPaint.setTypeface((Typeface) which.getNative()); + fillPaint.setTextSize(which.getDefaultSize()); } - //public void textFont(PFont which, float size) + @Override + public void textFont(PFont which, float size) { + super.textFont(which, size); + fillPaint.setTypeface((Typeface) which.getNative()); + fillPaint.setTextSize(size); + } //public void textLeading(float leading) @@ -1192,14 +1296,7 @@ public void textSize(float size) { fillPaint.setTextSize(size); } - // take care of setting the textSize and textLeading vars - // this has to happen second, because it calls textAscent() - // (which requires the native font metrics to be set) - textSize = size; -// PApplet.println("P2D textSize textAscent -> " + textAscent()); -// PApplet.println("P2D textSize textDescent -> " + textDescent()); - textLeading = (textAscent() + textDescent()) * 1.275f; -// PApplet.println("P2D textSize textLeading = " + textLeading); + handleTextSize(size); } @@ -1291,7 +1388,7 @@ protected void textLineImpl(char buffer[], int start, int stop, // textFont.smooth ? // RenderingHints.VALUE_ANTIALIAS_ON : // RenderingHints.VALUE_ANTIALIAS_OFF); - fillPaint.setAntiAlias(textFont.smooth); + fillPaint.setAntiAlias(textFont.isSmooth()); //System.out.println("setting frac metrics"); //g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, @@ -1304,7 +1401,7 @@ protected void textLineImpl(char buffer[], int start, int stop, // return to previous smoothing state if it was changed // canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antialias); - fillPaint.setAntiAlias(smooth); + fillPaint.setAntiAlias(0 < smooth); // textX = x + textWidthImpl(buffer, start, stop); // textY = y; @@ -1320,29 +1417,38 @@ protected void textLineImpl(char buffer[], int start, int stop, @Override public void pushMatrix() { -// if (transformCount == transformStack.length) { -// throw new RuntimeException("pushMatrix() cannot use push more than " + -// transformStack.length + " times"); -// } -// transformStack[transformCount] = canvas.getMatrix(); -// transformCount++; - canvas.save(Canvas.MATRIX_SAVE_FLAG); + if (transformCount == transformStack.length) { + throw new RuntimeException("pushMatrix() cannot use push more than " + + transformStack.length + " times"); + } + transform.get(transformStack[transformCount]); + transformCount++; + +// canvas.save(); } @Override public void popMatrix() { -// if (transformCount == 0) { -// throw new RuntimeException("missing a popMatrix() " + -// "to go with that pushMatrix()"); -// } -// transformCount--; -// canvas.setMatrix(transformStack[transformCount]); - canvas.restore(); + if (transformCount == 0) { + throw new RuntimeException("missing a popMatrix() " + + "to go with that pushMatrix()"); + } + transformCount--; + transform.set(transformStack[transformCount]); + updateTransformMatrix(); + + // Using canvas.restore() here and canvas.save() in popMatrix() and should achieve + // the same effect as setting copying transform into transformMatrix with updateTransformMatrix() + // and setting it below, although it has been reported that with the later approach, a push/pop + // would not result in the initial matrix state: + // https://github.com/processing/processing-android/issues/445 + // However, cannot find + canvas.setMatrix(transformMatrix); +// canvas.restore(); } - ////////////////////////////////////////////////////////////// // MATRIX TRANSFORMS @@ -1350,15 +1456,14 @@ public void popMatrix() { @Override public void translate(float tx, float ty) { + transform.translate(tx, ty); canvas.translate(tx, ty); } - //public void translate(float tx, float ty, float tz) - - @Override public void rotate(float angle) { + transform.rotate(angle); canvas.rotate(angle * RAD_TO_DEG); } @@ -1389,12 +1494,14 @@ public void rotate(float angle, float vx, float vy, float vz) { @Override public void scale(float s) { + transform.scale(s, s); canvas.scale(s, s); } @Override public void scale(float sx, float sy) { + transform.scale(sx, sy); canvas.scale(sx, sy); } @@ -1407,17 +1514,20 @@ public void scale(float sx, float sy, float sz) { @Override public void shearX(float angle) { - canvas.skew((float) Math.tan(angle), 0); + float t = (float) Math.tan(angle); + transform.apply(1, t, 0, 0, 1, 0); + canvas.skew(t, 0); } @Override public void shearY(float angle) { - canvas.skew(0, (float) Math.tan(angle)); + float t = (float) Math.tan(angle); + transform.apply(1, 0, 0, t, 1, 0); + canvas.skew(0, t); } - ////////////////////////////////////////////////////////////// // MATRIX MORE @@ -1425,8 +1535,8 @@ public void shearY(float angle) { @Override public void resetMatrix() { -// canvas.setTransform(new AffineTransform()); - canvas.setMatrix(new Matrix()); + transform.reset(); + canvas.setMatrix(null); } @@ -1436,15 +1546,9 @@ public void resetMatrix() { @Override public void applyMatrix(float n00, float n01, float n02, float n10, float n11, float n12) { -// canvas.transform(new AffineTransform(n00, n10, n01, n11, n02, n12)); - // TODO optimize - Matrix m = new Matrix(); - m.setValues(new float[] { - n00, n01, n02, - n10, n11, n12, - 0, 0, 1 - }); - canvas.concat(m); + transform.apply(n00, n01, n02, n10, n11, n12); + updateTransformMatrix(); + canvas.concat(transformMatrix); } @@ -1477,14 +1581,7 @@ public PMatrix2D getMatrix(PMatrix2D target) { if (target == null) { target = new PMatrix2D(); } -// canvas.getTransform().getMatrix(transform); - Matrix m = new Matrix(); - canvas.getMatrix(m); - m.getValues(transform); -// target.set((float) transform[0], (float) transform[2], (float) transform[4], -// (float) transform[1], (float) transform[3], (float) transform[5]); - target.set((float) transform[0], (float) transform[1], (float) transform[2], - (float) transform[3], (float) transform[4], (float) transform[5]); + target.set(transform); return target; } @@ -1501,16 +1598,9 @@ public PMatrix3D getMatrix(PMatrix3D target) { @Override public void setMatrix(PMatrix2D source) { -// canvas.setTransform(new AffineTransform(source.m00, source.m10, -// source.m01, source.m11, -// source.m02, source.m12)); - Matrix matrix = new Matrix(); - matrix.setValues(new float[] { - source.m00, source.m01, source.m02, - source.m10, source.m11, source.m12, - 0, 0, 1 - }); - canvas.setMatrix(matrix); + transform.set(source); + updateTransformMatrix(); + canvas.setMatrix(transformMatrix); } @@ -1526,6 +1616,28 @@ public void printMatrix() { } + protected Matrix getMatrixImp() { + Matrix m = new Matrix(); + updateTransformMatrix(); + m.set(transformMatrix); + return m; +// return canvas.getMatrix(); + } + + + public void updateTransformMatrix() { + transformArray[0] = transform.m00; + transformArray[1] = transform.m01; + transformArray[2] = transform.m02; + transformArray[3] = transform.m10; + transformArray[4] = transform.m11; + transformArray[5] = transform.m12; + transformArray[6] = 0; + transformArray[7] = 0; + transformArray[8] = 1; + transformMatrix.setValues(transformArray); + } + ////////////////////////////////////////////////////////////// @@ -1563,28 +1675,24 @@ public void printMatrix() { @Override public float screenX(float x, float y) { -// canvas.getTransform().getMatrix(transform); -// return (float)transform[0]*x + (float)transform[2]*y + (float)transform[4]; if (screenPoint == null) { screenPoint = new float[2]; } screenPoint[0] = x; screenPoint[1] = y; - canvas.getMatrix().mapPoints(screenPoint); + getMatrixImp().mapPoints(screenPoint); return screenPoint[0]; } @Override public float screenY(float x, float y) { -// canvas.getTransform().getMatrix(transform); -// return (float)transform[1]*x + (float)transform[3]*y + (float)transform[5]; if (screenPoint == null) { screenPoint = new float[2]; } screenPoint[0] = x; screenPoint[1] = y; - canvas.getMatrix().mapPoints(screenPoint); + getMatrixImp().mapPoints(screenPoint); return screenPoint[1]; } @@ -1898,6 +2006,11 @@ public void endRaw() { @Override public void loadPixels() { + if (bitmap == null) { + throw new RuntimeException("The pixels array is not available in this " + + "renderer withouth a backing bitmap"); + } + if ((pixels == null) || (pixels.length != width * height)) { pixels = new int[width * height]; } @@ -1915,6 +2028,11 @@ public void loadPixels() { */ @Override public void updatePixels() { + if (bitmap == null) { + throw new RuntimeException("The pixels array is not available in this " + + "renderer withouth a backing bitmap"); + } + // WritableRaster raster = ((BufferedImage) image).getRaster(); // raster.setDataElements(0, 0, width, height, pixels); bitmap.setPixels(pixels, 0, width, 0, 0, width, height); @@ -1944,6 +2062,94 @@ public void resize(int wide, int high) { } + @Override + protected void clearState() { + super.clearState(); + if (restoreFilename != null) { + File cacheFile = new File(restoreFilename); + cacheFile.delete(); + } + } + + + @Override + protected void saveState() { + super.saveState(); + + Context context = parent.getContext(); + if (context == null || bitmap == null || parent.getSurface().getComponent().isService()) return; + try { + // Saving current width and height to avoid restoring the screen after a screen rotation + restoreWidth = pixelWidth; + restoreHeight = pixelHeight; + + int size = bitmap.getHeight() * bitmap.getRowBytes(); + ByteBuffer restoreBitmap = ByteBuffer.allocate(size); + bitmap.copyPixelsToBuffer(restoreBitmap); + + // Tries to use external but if not mounted, falls back on internal storage, as shown in + // https://developer.android.com/topic/performance/graphics/cache-bitmap#java + File cacheDir = Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !isExternalStorageRemovable() ? + context.getExternalCacheDir() : context.getCacheDir(); + File cacheFile = new File(cacheDir + File.separator + "restore_pixels"); + restoreFilename = cacheFile.getAbsolutePath(); + + FileOutputStream stream = new FileOutputStream(cacheFile); + ObjectOutputStream dout = new ObjectOutputStream(stream); + byte[] array = new byte[size]; + restoreBitmap.rewind(); + restoreBitmap.get(array); + dout.writeObject(array); + dout.flush(); + stream.getFD().sync(); + stream.close(); + } catch (Exception ex) { + PGraphics.showWarning("Could not save screen contents to cache"); + ex.printStackTrace(); + } + } + + + @Override + protected void restoreSurface() { + if (changed) { + changed = false; + if (restoreFilename != null && restoreWidth == pixelWidth && restoreHeight == pixelHeight) { + // Set the counter to 1 so the restore bitmap is drawn in the next frame. + restoreCount = 1; + } + } else if (restoreCount > 0) { + restoreCount--; + if (restoreCount == 0) { + Context context = parent.getContext(); + if (context == null) return; + try { + // Load cached bitmap and draw + File cacheFile = new File(restoreFilename); + FileInputStream inStream = new FileInputStream(cacheFile); + ObjectInputStream din = new ObjectInputStream(inStream); + byte[] array = (byte[]) din.readObject(); + ByteBuffer restoreBitmap = ByteBuffer.wrap(array); + if (restoreBitmap.capacity() == bitmap.getHeight() * bitmap.getRowBytes()) { + restoreBitmap.rewind(); + bitmap.copyPixelsFromBuffer(restoreBitmap); + } + inStream.close(); + cacheFile.delete(); + } catch (Exception ex) { + PGraphics.showWarning("Could not restore screen contents from cache"); + ex.printStackTrace(); + } finally { + restoreFilename = null; + restoreWidth = -1; + restoreHeight = -1; + restoredSurface = true; + } + } + } + super.restoreSurface(); + } + ////////////////////////////////////////////////////////////// @@ -1955,7 +2161,7 @@ public void resize(int wide, int high) { @Override public int get(int x, int y) { - if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; + if ((bitmap == null) || (x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; // WritableRaster raster = ((BufferedImage) image).getRaster(); // raster.getDataElements(x, y, getset); // return getset[0]; @@ -1990,7 +2196,7 @@ public PImage get() { @Override public void set(int x, int y, int argb) { - if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return; + if ((bitmap == null) || (x < 0) || (y < 0) || (x >= width) || (y >= height)) return; // getset[0] = argb; // WritableRaster raster = ((BufferedImage) image).getRaster(); // raster.setDataElements(x, y, getset); @@ -2006,36 +2212,33 @@ public void set(int x, int y, PImage src) { throw new RuntimeException("set() not available for ALPHA images"); } - if (src.bitmap == null) { - // hopefully this will do the work to figure out what's on/offscreen - // in spite of the offset and stride that's been provided - canvas.drawBitmap(src.pixels, 0, src.width, - x, y, src.width, src.height, false, null); - // hasAlpha is set to false since we don't want blending. - // however that may be incorrect if it winds up copying only the RGB - // (without the A) portion of the pixels. - - } else { // src.bitmap != null - if (src.width != src.bitmap.getWidth() || - src.height != src.bitmap.getHeight()) { - src.bitmap.recycle(); - src.bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); - src.modified = true; - } - if (src.modified) { - if (!src.bitmap.isMutable()) { - src.bitmap.recycle(); - src.bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); - } - src.bitmap.setPixels(src.pixels, 0, src.width, 0, 0, src.width, src.height); - src.modified = false; + Bitmap bitmap = (Bitmap)src.getNative(); + if (bitmap == null) { + bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + src.setNative(bitmap); + src.setModified(); + } + if (src.width != bitmap.getWidth() || + src.height != bitmap.getHeight()) { + bitmap.recycle(); + bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + src.setNative(bitmap); + src.setModified(); + } + if (src.isModified()) { + if (!bitmap.isMutable()) { + bitmap.recycle(); + bitmap = Bitmap.createBitmap(src.width, src.height, Config.ARGB_8888); + setNative(bitmap); } - // set() happens in screen coordinates, so need to clear the ctm - canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.setMatrix(null); // set to identity - canvas.drawBitmap(src.bitmap, x, y, null); - canvas.restore(); + bitmap.setPixels(src.pixels, 0, src.width, 0, 0, src.width, src.height); + src.setModified(false); } + // set() happens in screen coordinates, so need to clear the ctm + pushMatrix(); + canvas.setMatrix(null); // set to identity + canvas.drawBitmap(bitmap, x, y, null); + popMatrix(); } @@ -2123,11 +2326,16 @@ public void mask(PImage alpha) { @Override public void copy(int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) { + if (bitmap == null) { + throw new RuntimeException("The pixels array is not available in this " + + "renderer withouth a backing bitmap"); + } + // Bitmap bitsy = Bitmap.createBitmap(image, sx, sy, sw, sh); // rect.set(dx, dy, dx + dw, dy + dh); // canvas.drawBitmap(bitsy, - rect.set(sx, sy, sx+sw, sy+sh); - Rect src = new Rect(dx, dy, dx+dw, dy+dh); + rect.set(dx, dy, dx+dw, dy+dh); + Rect src = new Rect(sx, sy, sx+sw, sy+sh); canvas.drawBitmap(bitmap, src, rect, null); // if ((sw != dw) || (sh != dh)) { diff --git a/libs/processing-core/src/main/java/processing/a2d/PShapeAndroid2D.java b/libs/processing-core/src/main/java/processing/a2d/PShapeAndroid2D.java new file mode 100644 index 000000000..b9bb2c019 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/a2d/PShapeAndroid2D.java @@ -0,0 +1,132 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.a2d; + +import android.graphics.Shader; + +import processing.core.PGraphics; +import processing.core.PShapeSVG; +import processing.data.XML; + +public class PShapeAndroid2D extends PShapeSVG { + protected Shader strokeGradientPaint; + protected Shader fillGradientPaint; + + + public PShapeAndroid2D(XML svg) { + super(svg); + } + + + public PShapeAndroid2D(PShapeSVG parent, XML properties, boolean parseKids) { + super(parent, properties, parseKids); + } + + + @Override + protected void setParent(PShapeSVG parent) { + super.setParent(parent); + + if (parent instanceof PShapeAndroid2D) { + PShapeAndroid2D pj = (PShapeAndroid2D) parent; + fillGradientPaint = pj.fillGradientPaint; + strokeGradientPaint = pj.strokeGradientPaint; + + } else { // parent is null or not Android2D + fillGradientPaint = null; + strokeGradientPaint = null; + } + } + + + /** Factory method for subclasses. */ + @Override + protected PShapeSVG createShape(PShapeSVG parent, XML properties, boolean parseKids) { + return new PShapeAndroid2D(parent, properties, parseKids); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + protected Shader calcGradientPaint(Gradient gradient) { + // TODO just do this with the other parsing + int[] colors = new int[gradient.count]; + int opacityMask = ((int) (opacity * 255)) << 24; + for (int i = 0; i < gradient.count; i++) { + colors[i] = opacityMask | (gradient.color[i] & 0xFFFFFF); + } + + if (gradient instanceof LinearGradient) { + LinearGradient grad = (LinearGradient) gradient; +// return new LinearGradientPaint(grad.x1, grad.y1, grad.x2, grad.y2, +// grad.offset, grad.color, grad.count, +// opacity); + return new android.graphics.LinearGradient(grad.x1, grad.y1, + grad.x2, grad.y2, + colors, grad.offset, + Shader.TileMode.CLAMP ); + + } else if (gradient instanceof RadialGradient) { + RadialGradient grad = (RadialGradient) gradient; +// return new RadialGradientPaint(grad.cx, grad.cy, grad.r, +// grad.offset, grad.color, grad.count, +// opacity); + return new android.graphics.RadialGradient(grad.cx, grad.cy, grad.r, + colors, grad.offset, + Shader.TileMode.CLAMP); + } + return null; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + @Override + protected void styles(PGraphics g) { + super.styles(g); + + if (g instanceof PGraphicsAndroid2D) { + PGraphicsAndroid2D gg = (PGraphicsAndroid2D) g; + + if (strokeGradient != null) { +// gg.strokeGradient = true; + if (strokeGradientPaint == null) { + strokeGradientPaint = calcGradientPaint(strokeGradient); + } + gg.strokePaint.setShader(strokeGradientPaint); + } + if (fillGradient != null) { +// gg.fillGradient = true; + if (fillGradientPaint == null) { + fillGradientPaint = calcGradientPaint(fillGradient); + } + gg.fillPaint.setShader(fillGradientPaint); + } else { + // need to shut off, in case parent object has a gradient applied + //gg.fillGradient = false; + } + } + } +} diff --git a/libs/processing-core/src/main/java/processing/a2d/PSurfaceAndroid2D.java b/libs/processing-core/src/main/java/processing/a2d/PSurfaceAndroid2D.java new file mode 100644 index 000000000..eb7635b87 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/a2d/PSurfaceAndroid2D.java @@ -0,0 +1,159 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.a2d; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.service.wallpaper.WallpaperService; +import android.support.wearable.watchface.CanvasWatchFaceService; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +import processing.android.AppComponent; +import processing.android.PFragment; +import processing.core.PApplet; +import processing.core.PGraphics; +import processing.core.PSurfaceNone; + +public class PSurfaceAndroid2D extends PSurfaceNone { + + public PSurfaceAndroid2D() { } + + public PSurfaceAndroid2D(PGraphics graphics, AppComponent component, SurfaceHolder holder) { + this.sketch = graphics.parent; + this.graphics = graphics; + this.component = component; + if (component.getKind() == AppComponent.FRAGMENT) { + PFragment frag = (PFragment)component; + activity = frag.getActivity(); + surfaceView = new SurfaceViewAndroid2D(activity, null); + } else if (component.getKind() == AppComponent.WALLPAPER) { + wallpaper = (WallpaperService)component; + surfaceView = new SurfaceViewAndroid2D(wallpaper, holder); + } else if (component.getKind() == AppComponent.WATCHFACE) { + watchface = (CanvasWatchFaceService)component; + surfaceView = null; + // Set as ready here, as watch faces don't have a surface view with a + // surfaceCreate() event to do it. + surfaceReady = true; + } + } + + /////////////////////////////////////////////////////////// + + // SurfaceView + + public class SurfaceViewAndroid2D extends SurfaceView implements SurfaceHolder.Callback { + SurfaceHolder holder; + + public SurfaceViewAndroid2D(Context context, SurfaceHolder holder) { + super(context); + this.holder = holder; + +// println("surface holder"); + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed + SurfaceHolder h = getHolder(); + h.addCallback(this); +// surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU); // no longer needed. + +// println("setting focusable, requesting focus"); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); +// println("done making surface view"); + + surfaceReady = false; // Will be ready when the surfaceCreated() event is called + + // Solves screen flickering: + // https://github.com/processing/processing-android/issues/570 + setBackgroundColor(Color.argb(0, 0, 0, 0)); + getHolder().setFormat(PixelFormat.TRANSPARENT); + } + + @Override + public SurfaceHolder getHolder() { + if (holder == null) { + return super.getHolder(); + } else { + return holder; + } + } + + // part of SurfaceHolder.Callback + public void surfaceCreated(SurfaceHolder holder) { + surfaceReady = true; + if (requestedThreadStart) { + // Only start the thread once the surface has been created, otherwise it will not be able to draw + startThread(); + } + if (PApplet.DEBUG) { + System.out.println("surfaceCreated()"); + } + } + + + // part of SurfaceHolder.Callback + public void surfaceDestroyed(SurfaceHolder holder) { + if (PApplet.DEBUG) { + System.out.println("surfaceDestroyed()"); + } + } + + + // part of SurfaceHolder.Callback + public void surfaceChanged(SurfaceHolder holder, int format, int iwidth, int iheight) { + if (PApplet.DEBUG) { + System.out.println("SketchSurfaceView.surfaceChanged() " + iwidth + " " + iheight); + } + + sketch.surfaceChanged(); + sketch.setSize(iwidth, iheight); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + sketch.surfaceWindowFocusChanged(hasFocus); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + return sketch.surfaceTouchEvent(event); + } + + @Override + public boolean onKeyDown(int code, android.view.KeyEvent event) { + sketch.surfaceKeyDown(code, event); + return super.onKeyDown(code, event); + } + + @Override + public boolean onKeyUp(int code, android.view.KeyEvent event) { + sketch.surfaceKeyUp(code, event); + return super.onKeyUp(code, event); + } + } +} diff --git a/libs/processing-core/src/main/java/processing/android/ActivityAPI.java b/libs/processing-core/src/main/java/processing/android/ActivityAPI.java new file mode 100644 index 000000000..751ec2048 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/ActivityAPI.java @@ -0,0 +1,65 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.content.Intent; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.Window; +import android.app.FragmentManager; + + +// Methods that should be implemented in PApplet to maintain backward +// compatibility with (some) functionality available from Activity/Fragment +public interface ActivityAPI { + // Lifecycle events + public void onCreate(Bundle savedInstanceState); + public void onDestroy(); + public void onStart(); + public void onStop(); + public void onPause(); + public void onResume(); + + // Activity and intent events + public void onActivityResult(int requestCode, int resultCode, Intent data); + public void onNewIntent(Intent intent); + + // Menu API + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater); + public boolean onOptionsItemSelected(MenuItem item); + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo); + public boolean onContextItemSelected(MenuItem item); + public void setHasOptionsMenu(boolean hasMenu); + + // IO events + public void onBackPressed(); + + // Activity management + public FragmentManager getFragmentManager(); + public Window getWindow(); +} diff --git a/libs/processing-core/src/main/java/processing/android/AppComponent.java b/libs/processing-core/src/main/java/processing/android/AppComponent.java new file mode 100644 index 000000000..396e01126 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/AppComponent.java @@ -0,0 +1,52 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.content.Intent; + +import processing.core.PApplet; +import processing.core.PConstants; + +abstract public interface AppComponent extends PConstants { + static public final int FRAGMENT = 0; + static public final int WALLPAPER = 1; + static public final int WATCHFACE = 2; + + public void initDimensions(); + public int getDisplayWidth(); + public int getDisplayHeight(); + public float getDisplayDensity(); + public int getKind(); + public void setSketch(PApplet sketch); + public PApplet getSketch(); + + public boolean isService(); + public ServiceEngine getEngine(); + + public void startActivity(Intent intent); + + public void requestDraw(); + public boolean canDraw(); + + public void dispose(); +} diff --git a/libs/processing-core/src/main/java/processing/android/CompatUtils.java b/libs/processing-core/src/main/java/processing/android/CompatUtils.java new file mode 100644 index 000000000..e218e860b --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/CompatUtils.java @@ -0,0 +1,112 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2017-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.annotation.SuppressLint; +import android.os.Build; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.View; +import android.graphics.Point; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Compatibility utilities that work across versions of Android. Even though + * the mode sets API level 17 (Android 4.2) as the minimum target, because the + * core library could be used from another IDE and lower targets, then this + * compatibility methods are still needed. + */ +public class CompatUtils { + // Start at 15,000,000, taking into account the comment from Singed + // http://stackoverflow.com/a/39307421 + private static final AtomicInteger nextId = new AtomicInteger(15000000); + + + /** + * This method retrieves the "real" display metrics and size, without + * subtracting any window decor or applying any compatibility scale factors. + * @param display the Display object + * @param metrics the metrics to retrieve + * @param size the size to retrieve + */ + static public void getDisplayParams(Display display, + DisplayMetrics metrics, Point size) { + if (Build.VERSION_CODES.JELLY_BEAN_MR1 <= Build.VERSION.SDK_INT) { + display.getRealMetrics(metrics); + display.getRealSize(size); + } if (Build.VERSION_CODES.ICE_CREAM_SANDWICH <= Build.VERSION.SDK_INT) { + display.getMetrics(metrics); + // Use undocumented methods getRawWidth, getRawHeight + try { + size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display); + size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display); + } catch (Exception e) { + display.getSize(size); + } + } else { + display.getMetrics(metrics); + display.getSize(size); + } + } + + + /** + * This method generates a unique View ID's. Handles the lack of + * View.generateViewId() in Android versions lower than 17, using a technique + * based on fantouch's code at http://stackoverflow.com/a/21000252 + * @return view ID + */ + @SuppressLint("NewApi") + static public int getUniqueViewId() { + if (Build.VERSION_CODES.JELLY_BEAN_MR1 <= Build.VERSION.SDK_INT) { + return View.generateViewId(); + } else { + for (;;) { + final int result = nextId.get(); + // aapt-generated IDs have the high byte nonzero; clamp to the range under that. + int newValue = result + 1; + if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0. + if (nextId.compareAndSet(result, newValue)) { + return result; + } + } + } + } + + + /** + * This method returns the UTF-8 charset + * @return UTF-8 charset + */ + @SuppressLint("NewApi") + static public Charset getCharsetUTF8() { + if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) { + return StandardCharsets.UTF_8; + } else { + return Charset.forName("UTF-8"); + } + } +} diff --git a/libs/processing-core/src/main/java/processing/android/PFragment.java b/libs/processing-core/src/main/java/processing/android/PFragment.java new file mode 100644 index 000000000..8fe80d72c --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/PFragment.java @@ -0,0 +1,261 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.util.DisplayMetrics; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Point; +import android.os.Bundle; +import android.view.ContextMenu; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.ContextMenu.ContextMenuInfo; + +import androidx.annotation.IdRes; +import androidx.annotation.LayoutRes; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import processing.core.PApplet; + +public class PFragment extends Fragment implements AppComponent { + private DisplayMetrics metrics; + private Point size; + private PApplet sketch; + private @LayoutRes int layout = -1; + + + public PFragment() { + super(); + } + + + public PFragment(PApplet sketch) { + super(); + setSketch(sketch); + } + + + public void initDimensions() { + metrics = new DisplayMetrics(); + size = new Point(); + WindowManager wm = getActivity().getWindowManager(); + Display display = wm.getDefaultDisplay(); + CompatUtils.getDisplayParams(display, metrics, size); + } + + + public int getDisplayWidth() { + return size.x; + } + + + public int getDisplayHeight() { + return size.y; + } + + + public float getDisplayDensity() { + return metrics.density; + } + + + public int getKind() { + return FRAGMENT; + } + + + public void setSketch(PApplet sketch) { + this.sketch = sketch; + if (layout != -1) { + sketch.parentLayout = layout; + } + } + + + public PApplet getSketch() { + return sketch; + } + + + public void setLayout(@LayoutRes int layout, @IdRes int id, FragmentActivity activity) { + this.layout = layout; + if (sketch != null) { + sketch.parentLayout = layout; + } + FragmentManager manager = activity.getSupportFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + transaction.add(id, this); + transaction.commit(); + } + + + public void setView(View view, FragmentActivity activity) { + FragmentManager manager = activity.getSupportFragmentManager(); + FragmentTransaction transaction = manager.beginTransaction(); + transaction.add(view.getId(), this); + transaction.commit(); + } + + + public boolean isService() { + return false; + } + + + public ServiceEngine getEngine() { + return null; + } + + + public void dispose() { + } + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + if (sketch != null) { + sketch.initSurface(inflater, container, savedInstanceState, this, null); + + // For compatibility with older sketches that run some hardware initialization + // inside onCreate(), don't call from Fragment.onCreate() because the surface + // will not be yet ready, and so the reference to the activity and other + // system variables will be null. In any case, onCreateView() is called + // immediately after onCreate(): + // https://developer.android.com/reference/android/app/Fragment.html#Lifecycle + sketch.onCreate(savedInstanceState); + + return sketch.getSurface().getRootView(); + } else { + return null; + } + } + + + @Override + public void onStart() { + super.onStart(); + if (sketch != null) { + sketch.onStart(); + } + } + + + @Override + public void onResume() { + super.onResume(); + if (sketch != null) { + sketch.onResume(); + } + } + + + @Override + public void onPause() { + super.onPause(); + if (sketch != null) { + sketch.onPause(); + } + } + + + @Override + public void onStop() { + super.onStop(); + if (sketch != null) { + sketch.onStop(); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (sketch != null) { + sketch.onDestroy(); + } + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (sketch != null) sketch.onActivityResult(requestCode, resultCode, data); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (sketch != null) sketch.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (sketch != null) return sketch.onOptionsItemSelected(item); + return super.onOptionsItemSelected(item); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + if (sketch != null) sketch.onCreateContextMenu(menu, v, menuInfo); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (sketch != null) return sketch.onContextItemSelected(item); + return super.onContextItemSelected(item); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + if (PApplet.DEBUG) System.out.println("configuration changed: " + newConfig); + super.onConfigurationChanged(newConfig); + } + + + public void setOrientation(int which) { + if (which == PORTRAIT) { + getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } else if (which == LANDSCAPE) { + getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + } + + + public void requestDraw() { + } + + + public boolean canDraw() { + return sketch != null && sketch.isLooping(); + } +} diff --git a/libs/processing-core/src/main/java/processing/android/PWallpaper.java b/libs/processing-core/src/main/java/processing/android/PWallpaper.java new file mode 100644 index 000000000..850339100 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/PWallpaper.java @@ -0,0 +1,302 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.service.wallpaper.WallpaperService; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.WindowManager; +import android.util.DisplayMetrics; +import android.view.Display; +import android.graphics.Point; +import android.graphics.Rect; + +import processing.core.PApplet; + +public class PWallpaper extends WallpaperService implements AppComponent { + private Point size; + private DisplayMetrics metrics; + private WallpaperEngine engine; + + + public void initDimensions() { + metrics = new DisplayMetrics(); + size = new Point(); + WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + CompatUtils.getDisplayParams(display, metrics, size); + } + + + public int getDisplayWidth() { + return size.x; + } + + + public int getDisplayHeight() { + return size.y; + } + + + public float getDisplayDensity() { + return metrics.density; + } + + + public int getKind() { + return WALLPAPER; + } + + + public PApplet createSketch() { + return new PApplet(); + } + + + public void setSketch(PApplet sketch) { + engine.sketch = sketch; + } + + + public PApplet getSketch() { + return engine.sketch; + } + + + public boolean isService() { + return true; + } + + + public ServiceEngine getEngine() { + return engine; + } + + + public void requestDraw() { + } + + + public boolean canDraw() { + return true; + } + + + public void dispose() { + } + + + public void requestPermissions() { + } + + + @Override + public Engine onCreateEngine() { + engine = new WallpaperEngine(); + return engine; + } + + + @Override + public void onDestroy() { + super.onDestroy(); + + if (engine != null){ + //engine.sketch = null; + engine.onDestroy(); + } + } + + + public class WallpaperEngine extends Engine implements ServiceEngine { + PApplet sketch; + private float xOffset, xOffsetStep; + private float yOffset, yOffsetStep; + private int xPixelOffset, yPixelOffset; + + + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + super.onCreate(surfaceHolder); + sketch = createSketch(); + sketch.initSurface(PWallpaper.this, getSurfaceHolder()); + if (isPreview()) requestPermissions(); + setTouchEventsEnabled(true); + } + + + @Override + public void onSurfaceCreated(SurfaceHolder surfaceHolder) { + super.onSurfaceCreated(surfaceHolder); + } + + + @Override + public void onSurfaceChanged(final SurfaceHolder holder, final int format, + final int width, final int height) { + // When the surface of a live wallpaper changes (eg: after a screen rotation) the same sketch + // continues to run (unlike the case of regular apps, where its re-created) so we need to + // force a reset of the renderer so the backing FBOs (in the case of the OpenGL renderers) + // get reinitalized with the correct size. + sketch.g.reset(); + super.onSurfaceChanged(holder, format, width, height); + } + + + @Override + public void onVisibilityChanged(boolean visible) { + if (sketch != null) { + if (visible) { + sketch.onResume(); + } else { + sketch.onPause(); + } + } + super.onVisibilityChanged(visible); + } + + + /* + * Store the position of the touch event so we can use it for drawing + * later + */ + @Override + public void onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + if (sketch != null) { + sketch.surfaceTouchEvent(event); + } + } + + + @Override + public void onOffsetsChanged(float xOffset, float yOffset, + float xOffsetStep, float yOffsetStep, + int xPixelOffset, int yPixelOffset) { + + if (sketch != null) { + this.xOffset = xOffset; + this.yOffset = yOffset; + this.xOffsetStep = xOffsetStep; + this.yOffsetStep = yOffsetStep; + this.xPixelOffset = xPixelOffset; + this.yPixelOffset = yPixelOffset; + } + } + + + @Override + public void onSurfaceDestroyed(SurfaceHolder holder) { + // This is called immediately before a surface is being destroyed. + // After returning from this call, you should no longer try to access this + // surface. If you have a rendering thread that directly accesses the + // surface, you must ensure that thread is no longer touching the Surface + // before returning from this function. + super.onSurfaceDestroyed(holder); + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (sketch != null) { + sketch.onDestroy(); + } + } + + + @Override + public float getXOffset() { + return xOffset; + } + + + @Override + public float getYOffset() { + return yOffset; + } + + + @Override + public float getXOffsetStep() { + return xOffsetStep; + } + + + @Override + public float getYOffsetStep() { + return yOffsetStep; + } + + + @Override + public int getXPixelOffset() { + return xPixelOffset; + } + + + @Override + public int getYPixelOffset() { + return yPixelOffset; + } + + + @Override + public boolean isInAmbientMode() { + return false; + } + + + @Override + public boolean isRound() { + return false; + } + + + @Override + public Rect getInsets() { + return null; + } + + + @Override + public boolean useLowBitAmbient() { + return false; + } + + + @Override + public boolean requireBurnInProtection() { + return false; + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults) { + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + } +} diff --git a/libs/processing-core/src/main/java/processing/android/PWatchFaceCanvas.java b/libs/processing-core/src/main/java/processing/android/PWatchFaceCanvas.java new file mode 100644 index 000000000..ee4222dfc --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/PWatchFaceCanvas.java @@ -0,0 +1,368 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import java.lang.reflect.Method; + +import android.annotation.TargetApi; +import android.graphics.Canvas; +import android.graphics.Point; +import android.os.Bundle; +import android.graphics.Rect; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.watchface.CanvasWatchFaceService; +import android.support.wearable.watchface.WatchFaceStyle; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.WindowInsets; +import android.view.WindowManager; + +import processing.a2d.PGraphicsAndroid2D; +import processing.core.PApplet; + +@TargetApi(21) +public class PWatchFaceCanvas extends CanvasWatchFaceService implements AppComponent { + private Point size; + private DisplayMetrics metrics; + private CanvasEngine engine; + + public void initDimensions() { + metrics = new DisplayMetrics(); + size = new Point(); + WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + CompatUtils.getDisplayParams(display, metrics, size); + } + + + public int getDisplayWidth() { + return size.x; + } + + + public int getDisplayHeight() { + return size.y; + } + + + public float getDisplayDensity() { + return metrics.density; + } + + + public int getKind() { + return WATCHFACE; + } + + + public PApplet createSketch() { + return new PApplet(); + } + + + public void setSketch(PApplet sketch) { + engine.sketch = sketch; + } + + + public PApplet getSketch() { + return engine.sketch; + } + + + public boolean isService() { + return true; + } + + + public ServiceEngine getEngine() { + return engine; + } + + + public void requestDraw() { + if (engine != null) engine.invalidateIfNecessary(); + } + + + public boolean canDraw() { + // The rendering loop should never call handleDraw() directly, + // it only needs to invalidate the screen + return false; + } + + + public void dispose() { + } + + + public void requestPermissions() { + } + + + @Override + public Engine onCreateEngine() { + engine = new CanvasEngine(); + return engine; + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (engine != null) engine.onDestroy(); + } + + + private class CanvasEngine extends CanvasWatchFaceService.Engine implements ServiceEngine { + private PApplet sketch; + private Method compUpdatedMethod; + private Method tapCommandMethod; + private boolean isRound = false; + private Rect insets = new Rect(); + private boolean lowBitAmbient = false; + private boolean burnInProtection = false; + + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + super.onCreate(surfaceHolder); + setWatchFaceStyle(new WatchFaceStyle.Builder(PWatchFaceCanvas.this) + .setAcceptsTapEvents(true) + .build()); + sketch = createSketch(); + PGraphicsAndroid2D.useBitmap = false; + sketch.initSurface(PWatchFaceCanvas.this, null); + initTapEvents(); + initComplications(); + requestPermissions(); + } + + + private void initTapEvents() { + try { + tapCommandMethod = sketch.getClass().getMethod("onTapCommand", + new Class[] {int.class, int.class, int.class, long.class}); + } catch (Exception e) { + tapCommandMethod = null; + } + } + + + private void initComplications() { + try { + compUpdatedMethod = sketch.getClass().getMethod("onComplicationDataUpdate", + new Class[] {int.class, ComplicationData.class}); + } catch (Exception e) { + compUpdatedMethod = null; + } + } + + + private void invalidateIfNecessary() { + if (isVisible() && !isInAmbientMode()) { + invalidate(); + } + } + + + @Override + public void onAmbientModeChanged(boolean inAmbientMode) { + super.onAmbientModeChanged(inAmbientMode); + invalidateIfNecessary(); + // call new event handlers in sketch (?) + } + + + @Override + public void onPropertiesChanged(Bundle properties) { + super.onPropertiesChanged(properties); + lowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); + burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false); + } + + + @Override + public void onApplyWindowInsets(WindowInsets insets) { + super.onApplyWindowInsets(insets); + isRound = insets.isRound(); + this.insets.set(insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); + } + + + @Override + public void onVisibilityChanged(boolean visible) { + super.onVisibilityChanged(visible); + if (sketch != null) { + if (visible) { + sketch.onResume(); + } else { + sketch.onPause(); + } + } + } + + + @Override + public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) { + super.onSurfaceChanged(holder, format, width, height); + if (sketch != null) { + sketch.surfaceChanged(); + sketch.setSize(width, height); + } + } + + + @Override + public void onPeekCardPositionUpdate(Rect rect) { } + + + @Override + public void onTimeTick() { + invalidate(); + } + + + @Override + public void onDraw(Canvas canvas, Rect bounds) { + super.onDraw(canvas, bounds); + if (sketch != null) { + PGraphicsAndroid2D g2 = (PGraphicsAndroid2D)sketch.g; + g2.canvas = canvas; + sketch.handleDraw(); + } + } + + + @Override + public void onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + if (sketch != null) sketch.surfaceTouchEvent(event); + } + + + @Override + public void onTapCommand(@TapType int tapType, int x, int y, long eventTime) { + if (tapCommandMethod != null) { + try { + tapCommandMethod.invoke(tapType, x, y, eventTime); + } catch (Exception e) { } + invalidate(); + } + } + + + @Override + public void onComplicationDataUpdate(int complicationId, + ComplicationData complicationData) { + if (compUpdatedMethod != null) { + try { + compUpdatedMethod.invoke(complicationId, complicationData); + } catch (Exception e) { } + invalidate(); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (sketch != null) { + sketch.onDestroy(); + } + } + + + @Override + public float getXOffset() { + return 0; + } + + + @Override + public float getYOffset() { + return 0; + } + + + @Override + public float getXOffsetStep() { + return 0; + } + + + @Override + public float getYOffsetStep() { + return 0; + } + + + @Override + public int getXPixelOffset() { + return 0; + } + + + @Override + public int getYPixelOffset() { + return 0; + } + + + @Override + public boolean isRound() { + return isRound; + } + + + @Override + public Rect getInsets() { + return insets; + } + + + @Override + public boolean useLowBitAmbient() { + return lowBitAmbient; + } + + + @Override + public boolean requireBurnInProtection() { + return burnInProtection; + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults) { + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + } +} diff --git a/libs/processing-core/src/main/java/processing/android/PWatchFaceGLES.java b/libs/processing-core/src/main/java/processing/android/PWatchFaceGLES.java new file mode 100644 index 000000000..e62ab7eb4 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/PWatchFaceGLES.java @@ -0,0 +1,393 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.annotation.TargetApi; +import android.graphics.Point; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLDisplay; +import android.os.Bundle; +import android.view.Display; +import android.view.WindowInsets; +import android.support.wearable.complications.ComplicationData; +import android.support.wearable.watchface.Gles2WatchFaceService; +import android.support.wearable.watchface.WatchFaceStyle; +import android.util.DisplayMetrics; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.WindowManager; +import android.graphics.Rect; + +import java.lang.reflect.Method; + +import processing.core.PApplet; + +@TargetApi(21) +public class PWatchFaceGLES extends Gles2WatchFaceService implements AppComponent { + private static final int[] CONFIG_ATTRIB_LIST = new int[]{ + EGL14.EGL_RENDERABLE_TYPE, 4, + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_ALPHA_SIZE, 8, + EGL14.EGL_DEPTH_SIZE, 16, // this was missing + EGL14.EGL_NONE}; + + private Point size; + private DisplayMetrics metrics; + private GLES2Engine engine; + + + public void initDimensions() { + metrics = new DisplayMetrics(); + size = new Point(); + WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + CompatUtils.getDisplayParams(display, metrics, size); + } + + + public int getDisplayWidth() { + return size.x; + } + + + public int getDisplayHeight() { + return size.y; + } + + + public float getDisplayDensity() { + return metrics.density; + } + + + public int getKind() { + return WATCHFACE; + } + + + public PApplet createSketch() { + return new PApplet(); + } + + + public void setSketch(PApplet sketch) { + engine.sketch = sketch; + } + + + public PApplet getSketch() { + return engine.sketch; + } + + + public boolean isService() { + return true; + } + + + public ServiceEngine getEngine() { + return engine; + } + + + public void requestDraw() { + if (engine != null) engine.invalidateIfNecessary(); + } + + + public boolean canDraw() { + // The rendering loop should never call handleDraw() directly, it only needs to invalidate the + // screen + return false; + } + + + public void dispose() { + } + + + public void requestPermissions() { + } + + + @Override + public Engine onCreateEngine() { + engine = new GLES2Engine(); + return engine; + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (engine != null) engine.onDestroy(); + } + + + private class GLES2Engine extends Gles2WatchFaceService.Engine implements ServiceEngine { + private PApplet sketch; + private Method compUpdatedMethod; + private Method tapCommandMethod; + private boolean isRound = false; + private Rect insets = new Rect(); + private boolean lowBitAmbient = false; + private boolean burnInProtection = false; + + @Override + public void onCreate(SurfaceHolder surfaceHolder) { + super.onCreate(surfaceHolder); + setWatchFaceStyle(new WatchFaceStyle.Builder(PWatchFaceGLES.this) + .setAcceptsTapEvents(true) + .build()); + sketch = createSketch(); + sketch.initSurface(PWatchFaceGLES.this, null); + initTapEvents(); + initComplications(); + requestPermissions(); + } + + + public EGLConfig chooseEglConfig(EGLDisplay eglDisplay) { + int[] numEglConfigs = new int[1]; + EGLConfig[] eglConfigs = new EGLConfig[1]; + if (!EGL14.eglChooseConfig(eglDisplay, CONFIG_ATTRIB_LIST, 0, eglConfigs, 0, eglConfigs.length, numEglConfigs, 0)) { + throw new RuntimeException("eglChooseConfig failed"); + } else if (numEglConfigs[0] == 0) { + throw new RuntimeException("no matching EGL configs"); + } else { + return eglConfigs[0]; + } + } + + @Override + public void onGlContextCreated() { + super.onGlContextCreated(); + } + + + @Override + public void onGlSurfaceCreated(int width, int height) { + super.onGlSurfaceCreated(width, height); + if (sketch != null) { + sketch.surfaceChanged(); + sketch.setSize(width, height); + } + } + + + private void initTapEvents() { + try { + tapCommandMethod = sketch.getClass().getMethod("onTapCommand", + new Class[] {int.class, int.class, int.class, long.class}); + } catch (Exception e) { + tapCommandMethod = null; + } + } + + + private void initComplications() { + try { + compUpdatedMethod = sketch.getClass().getMethod("onComplicationDataUpdate", + new Class[] {int.class, ComplicationData.class}); + } catch (Exception e) { + compUpdatedMethod = null; + } + } + + + private void invalidateIfNecessary() { + if (isVisible() && !isInAmbientMode()) { + invalidate(); + } + } + + + @Override + public void onAmbientModeChanged(boolean inAmbientMode) { + super.onAmbientModeChanged(inAmbientMode); + invalidateIfNecessary(); + // call new event handlers in sketch (?) + } + + + @Override + public void onPropertiesChanged(Bundle properties) { + super.onPropertiesChanged(properties); + lowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false); + burnInProtection = properties.getBoolean(PROPERTY_BURN_IN_PROTECTION, false); + } + + + @Override + public void onApplyWindowInsets(WindowInsets insets) { + super.onApplyWindowInsets(insets); + this.insets.set(insets.getSystemWindowInsetLeft(), + insets.getSystemWindowInsetTop(), + insets.getSystemWindowInsetRight(), + insets.getSystemWindowInsetBottom()); + } + + + @Override + public void onVisibilityChanged(boolean visible) { + super.onVisibilityChanged(visible); + if (sketch != null) { + if (visible) { + sketch.onResume(); + } else { + sketch.onPause(); + } + } + } + + + @Override + public void onPeekCardPositionUpdate(Rect rect) { } + + + @Override + public void onTimeTick() { + invalidate(); + } + + + @Override + public void onDraw() { + super.onDraw(); + if (sketch != null) sketch.handleDraw(); + } + + + @Override + public void onTouchEvent(MotionEvent event) { + super.onTouchEvent(event); + if (sketch != null) sketch.surfaceTouchEvent(event); + } + + + @Override + public void onTapCommand(@TapType int tapType, int x, int y, long eventTime) { + if (tapCommandMethod != null) { + try { + tapCommandMethod.invoke(tapType, x, y, eventTime); + } catch (Exception e) { } + invalidate(); + } + } + + + @Override + public void onComplicationDataUpdate(int complicationId, + ComplicationData complicationData) { + if (compUpdatedMethod != null) { + try { + compUpdatedMethod.invoke(complicationId, complicationData); + } catch (Exception e) { + } + invalidate(); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (sketch != null) { + sketch.onDestroy(); + } + } + + + @Override + public float getXOffset() { + return 0; + } + + + @Override + public float getYOffset() { + return 0; + } + + + @Override + public float getXOffsetStep() { + return 0; + } + + + @Override + public float getYOffsetStep() { + return 0; + } + + + @Override + public int getXPixelOffset() { + return 0; + } + + + @Override + public int getYPixelOffset() { + return 0; + } + + + @Override + public boolean isRound() { + return isRound; + } + + + @Override + public Rect getInsets() { + return insets; + } + + + @Override + public boolean useLowBitAmbient() { + return lowBitAmbient; + } + + + @Override + public boolean requireBurnInProtection() { + return burnInProtection; + } + + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults) { + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + } +} diff --git a/libs/processing-core/src/main/java/processing/android/PermissionRequestor.java b/libs/processing-core/src/main/java/processing/android/PermissionRequestor.java new file mode 100644 index 000000000..deac0d852 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/PermissionRequestor.java @@ -0,0 +1,61 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2017-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.app.Activity; +import android.os.Bundle; + +import android.support.v4.os.ResultReceiver; +import androidx.core.app.ActivityCompat; +import androidx.annotation.RestrictTo; + +// A simple utility activity to request permissions in a service. +public class PermissionRequestor extends Activity { + public static final String KEY_RESULT_RECEIVER = "resultReceiver"; + public static final String KEY_PERMISSIONS = "permissions"; + public static final String KEY_GRANT_RESULTS = "grantResults"; + public static final String KEY_REQUEST_CODE = "requestCode"; + + ResultReceiver resultReceiver; + String[] permissions; + int requestCode; + + @Override + protected void onStart() { + super.onStart(); + resultReceiver = this.getIntent().getParcelableExtra(KEY_RESULT_RECEIVER); + permissions = this.getIntent().getStringArrayExtra(KEY_PERMISSIONS); + requestCode = this.getIntent().getIntExtra(KEY_REQUEST_CODE, 0); + ActivityCompat.requestPermissions(this, permissions, requestCode); + } + + @Override + @SuppressWarnings("RestrictedApi") + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + Bundle resultData = new Bundle(); + resultData.putStringArray(KEY_PERMISSIONS, permissions); + resultData.putIntArray(KEY_GRANT_RESULTS, grantResults); + resultReceiver.send(requestCode, resultData); + finish(); + } +} \ No newline at end of file diff --git a/libs/processing-core/src/main/java/processing/android/ServiceEngine.java b/libs/processing-core/src/main/java/processing/android/ServiceEngine.java new file mode 100644 index 000000000..11b1164f0 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/android/ServiceEngine.java @@ -0,0 +1,50 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2017-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.android; + +import android.graphics.Rect; + +import processing.core.PConstants; + +public interface ServiceEngine extends PConstants { + // wallpapers + public boolean isPreview(); + public float getXOffset(); + public float getYOffset(); + public float getXOffsetStep(); + public float getYOffsetStep(); + public int getXPixelOffset(); + public int getYPixelOffset(); + + // wear + public boolean isInAmbientMode(); + public boolean isRound(); + public Rect getInsets(); + public boolean useLowBitAmbient(); + public boolean requireBurnInProtection(); + + // Service permissions + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults); +} diff --git a/core/src/processing/core/PApplet.java b/libs/processing-core/src/main/java/processing/core/PApplet.java similarity index 53% rename from core/src/processing/core/PApplet.java rename to libs/processing-core/src/main/java/processing/core/PApplet.java index f3e1cfa54..2b55cf67c 100644 --- a/core/src/processing/core/PApplet.java +++ b/libs/processing-core/src/main/java/processing/core/PApplet.java @@ -3,6 +3,7 @@ /* Part of the Processing project - http://processing.org + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology @@ -23,55 +24,111 @@ package processing.core; -import java.io.*; -import java.lang.reflect.*; -import java.net.*; -import java.text.NumberFormat; -import java.util.*; -import java.util.regex.*; -import java.util.zip.*; - -import android.app.*; -import android.content.*; -import android.content.pm.ActivityInfo; -import android.content.pm.ConfigurationInfo; +import android.app.Activity; +import android.app.FragmentManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.graphics.*; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Rect; +import android.graphics.Typeface; import android.net.Uri; -import android.opengl.GLSurfaceView; +import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.text.format.Time; -import android.util.*; +import android.os.Looper; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.MotionEvent; import android.view.SurfaceHolder; -import android.view.SurfaceView; -import android.view.ViewGroup.LayoutParams; +import android.view.View; +import android.view.ViewGroup; import android.view.Window; -import android.view.WindowManager; -import android.widget.*; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.LayoutRes; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Random; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import processing.a2d.PGraphicsAndroid2D; +import processing.android.ActivityAPI; +import processing.android.AppComponent; +import processing.android.CompatUtils; +import processing.data.JSONArray; +import processing.data.JSONObject; +import processing.data.StringList; +import processing.data.Table; +import processing.data.XML; +import processing.event.Event; +import processing.event.KeyEvent; +import processing.event.MouseEvent; +import processing.event.TouchEvent; +import processing.opengl.PGL; +import processing.opengl.PGraphics2D; +import processing.opengl.PGraphics3D; +import processing.opengl.PShader; + +public class PApplet extends Object implements ActivityAPI, PConstants { -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.HttpResponse; -import org.apache.http.HttpEntity; + static final public boolean DEBUG = false; +// static final public boolean DEBUG = true; -import processing.data.*; -import processing.event.*; -import processing.opengl.*; + // Convenience public constant holding the SDK version, akin to platform in Java mode + static final public int SDK = Build.VERSION.SDK_INT; + //static final public int SDK = Build.VERSION_CODES.ICE_CREAM_SANDWICH; // Forcing older SDK for testing -public class PApplet extends Activity implements PConstants, Runnable { - /** The PGraphics renderer associated with this PApplet */ - public PGraphics g; + /** + * The surface this sketch draws to. + */ + protected PSurface surface; -// static final public boolean DEBUG = true; - static final public boolean DEBUG = false; + /** + * The view group containing the surface view of the PApplet. + */ + public @LayoutRes int parentLayout = -1; - /** The frame containing this applet (if any) */ -// public Frame frame; + /** The PGraphics renderer associated with this PApplet */ + public PGraphics g; /** * The screen size when the sketch was started. This is initialized inside @@ -105,29 +162,8 @@ public class PApplet extends Activity implements PConstants, Runnable { // static final boolean THREAD_DEBUG = false; /** Default width and height for applet when not specified */ -// static public final int DEFAULT_WIDTH = 100; -// static public final int DEFAULT_HEIGHT = 100; - - /** - * Minimum dimensions for the window holding an applet. - * This varies between platforms, Mac OS X 10.3 can do any height - * but requires at least 128 pixels width. Windows XP has another - * set of limitations. And for all I know, Linux probably lets you - * make windows with negative sizes. - */ -// static public final int MIN_WINDOW_WIDTH = 128; -// static public final int MIN_WINDOW_HEIGHT = 128; - - /** - * Exception thrown when size() is called the first time. - *

- * This is used internally so that setup() is forced to run twice - * when the renderer is changed. This is the only way for us to handle - * invoking the new renderer while also in the midst of rendering. - */ - static public class RendererChangeException extends RuntimeException { } - - protected boolean surfaceReady; + static public final int DEFAULT_WIDTH = -1; + static public final int DEFAULT_HEIGHT = -1; /** * Set true when the surface dimensions have changed, so that the PGraphics @@ -135,16 +171,6 @@ static public class RendererChangeException extends RuntimeException { } */ protected boolean surfaceChanged; - /** - * true if no size() command has been executed. This is used to wait until - * a size has been set before placing in the window and showing it. - */ -// public boolean defaultSize; - -// volatile boolean resizeRequest; -// volatile int resizeWidth; -// volatile int resizeHeight; - /** * Pixel buffer from this applet's PGraphics. *

@@ -154,13 +180,28 @@ static public class RendererChangeException extends RuntimeException { } public int[] pixels; /** width of this applet's associated PGraphics */ - public int width; + public int width = DEFAULT_WIDTH; /** height of this applet's associated PGraphics */ - public int height; + public int height = DEFAULT_HEIGHT; - // can't call this because causes an ex, but could set elsewhere - //final float screenDensity = getResources().getDisplayMetrics().density; + /** The logical density of the display from getDisplayMetrics().density + * According to Android's documentation: + * This is a scaling factor for the Density Independent Pixel unit, + * where one DIP is one pixel on an approximately 160 dpi screen + * (for example a 240x320, 1.5"x2" screen), providing the baseline of the + * system's display. Thus on a 160dpi screen this density value will be 1; + * on a 120 dpi screen it would be .75; etc. + */ + public float displayDensity = 1; + + // For future use + public int pixelDensity = 1; + public int pixelWidth; + public int pixelHeight; + + /////////////////////////////////////////////////////////////// + // Mouse events /** absolute x position of input on screen */ public int mouseX; @@ -168,37 +209,6 @@ static public class RendererChangeException extends RuntimeException { } /** absolute x position of input on screen */ public int mouseY; -// /** current x position of motion (relative to start of motion) */ -// public float motionX; -// -// /** current y position of the mouse (relative to start of motion) */ -// public float motionY; -// -// /** Last reported pressure of the current motion event */ -// public float motionPressure; -// -// /** Last reported positions and pressures for all pointers */ -// protected int numPointers; -// protected int pnumPointers; -// -// protected float[] ppointersX = {0}; -// protected float[] ppointersY = {0}; -// protected float[] ppointersPressure = {0}; -// -// protected float[] pointersX = {0}; -// protected float[] pointersY = {0}; -// protected float[] pointersPressure = {0}; -// -// protected int downMillis; -// protected float downX, downY; -// protected boolean onePointerGesture = false; -// protected boolean twoPointerGesture = true; -// -// protected final int MIN_SWIPE_LENGTH = 150; // Minimum length (in pixels) of a swipe event -// protected final int MAX_SWIPE_DURATION = 2000; // Maximum duration (in millis) of a swipe event -// protected final int MAX_TAP_DISP = 20; // Maximum displacement (in pixels) during a tap event -// protected final int MAX_TAP_DURATION = 1000; // Maximum duration (in millis) of a tap event - /** * Previous x/y position of the mouse. This will be a different value @@ -210,14 +220,24 @@ static public class RendererChangeException extends RuntimeException { } * you're gonna run into trouble. */ public int pmouseX, pmouseY; -// public float pmotionX, pmotionY; + + public int mouseButton; + + public boolean mousePressed; + + + public boolean touchIsStarted; + + + public TouchEvent.Pointer[] touches = new TouchEvent.Pointer[0]; + /** * previous mouseX/Y for the draw loop, separated out because this is * separate from the pmouseX/Y when inside the mouse event handlers. */ protected int dmouseX, dmouseY; -// protected float dmotionX, dmotionY; + /** * pmotionX/Y for the event handlers (motionPressed(), motionDragged() etc) @@ -227,27 +247,21 @@ static public class RendererChangeException extends RuntimeException { } * to be updated once per trip through draw(). */ protected int emouseX, emouseY; -// protected float emotionX, emotionY; -// /** -// * Used to set pmotionX/Y to motionX/Y the first time motionX/Y are used, -// * otherwise pmotionX/Y are always zero, causing a nasty jump. -// *

-// * Just using (frameCount == 0) won't work since motionXxxxx() -// * may not be called until a couple frames into things. -// */ -// public boolean firstMotion; -// public int mouseButton; - - public boolean mousePressed; + /** + * ID of the pointer tracked for mouse events. + */ + protected int mousePointerId; -// public MouseEvent mouseEvent; -// public MotionEvent motionEvent; + /** + * ID of the most recently touch pointer gone up or down. + */ + protected int touchPointerId; - /** Post events to the main thread that created the Activity */ - Handler handler; + /////////////////////////////////////////////////////////////// + // Key events /** * Last key pressed. @@ -281,16 +295,44 @@ static public class RendererChangeException extends RuntimeException { } */ public boolean focused = false; - protected boolean windowFocused = false; - protected boolean viewFocused = false; + /** + * Keeps track of ENABLE_KEY_REPEAT hint + */ + protected boolean keyRepeatEnabled = false; /** - * true if the applet is online. - *

- * This can be used to test how the applet should behave - * since online situations are different (no file writing, etc). + * Set to open when openKeyboard() is called, and used to close the keyboard when the sketch is + * paused, otherwise it remains visible. + */ + boolean keyboardIsOpen = false; + + /** + * Flag to determine if the back key was pressed. + */ + private boolean requestedBackPress = false; + + /** + * Flag to determine if the user handled the back press. + */ + public boolean handledBackPressed = true; + + /////////////////////////////////////////////////////////////// + // Permission handling + + /** + * Callback methods to handle permission requests + */ + protected HashMap permissionMethods = new HashMap(); + + + /** + * Permissions requested during one frame */ -// public boolean online = false; + protected ArrayList reqPermissions = new ArrayList(); + + + /////////////////////////////////////////////////////////////// + // Rendering/timing /** * Time in milliseconds when the applet was started. @@ -299,6 +341,11 @@ static public class RendererChangeException extends RuntimeException { } */ long millisOffset = System.currentTimeMillis(); + protected boolean insideDraw; + + /** Last time in nanoseconds that frameRate was checked */ + protected long frameRateLastNanos = 0; + /** * The current value of frames per second. *

@@ -309,12 +356,6 @@ static public class RendererChangeException extends RuntimeException { } * As such, this value won't be valid until after 5-10 frames. */ public float frameRate = 10; - /** Last time in nanoseconds that frameRate was checked */ - protected long frameRateLastNanos = 0; - - /** As of release 0116, frameRate(60) is called as a default */ - protected float frameRateTarget = 60; - protected long frameRatePeriod = 1000000000L / 60L; protected boolean looping; @@ -337,27 +378,35 @@ static public class RendererChangeException extends RuntimeException { } */ public boolean finished; - /** - * For Android, true if the activity has been paused. - */ - protected boolean paused; - - protected SurfaceView surfaceView; - - /** - * The Window object for Android. - */ -// protected Window window; - /** * true if exit() has been called so that things shut down * once the main thread kicks off. */ protected boolean exitCalled; - Thread thread; + boolean insideSettings; + + String renderer = JAVA2D; + + int smooth = 1; // default smoothing (whatever that means for the renderer) + + boolean fullScreen = false; + + int display = -1; // use default + + // Background default needs to be different from the default value in + // PGraphics.backgroundColor, otherwise size(100, 100) bg spills over. + // https://github.com/processing/processing/issues/2297 + int windowColor = 0xffDDDDDD; + + /////////////////////////////////////////////////////////////// + // Error messages + + static final String ERROR_MIN_MAX = + "Cannot use min() or max() on an empty array."; - // messages to send if attached as an external vm + /////////////////////////////////////////////////////////////// + // Command line options /** * Position of the upper-lefthand corner of the editor window @@ -415,579 +464,441 @@ static public class RendererChangeException extends RuntimeException { } /** true if this sketch is being run by the PDE */ boolean external = false; - static final String ERROR_MIN_MAX = - "Cannot use min() or max() on an empty array."; - ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// + /** + * Required empty constructor. + */ + public PApplet() { - /** Called with the activity is first created. */ - @SuppressWarnings("unchecked") - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); -// println("PApplet.onCreate()"); - - if (DEBUG) println("onCreate() happening here: " + Thread.currentThread().getName()); - - Window window = getWindow(); - - // Take up as much area as possible - requestWindowFeature(Window.FEATURE_NO_TITLE); - window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); - - // This does the actual full screen work - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - - DisplayMetrics dm = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(dm); - displayWidth = dm.widthPixels; - displayHeight = dm.heightPixels; -// println("density is " + dm.density); -// println("densityDpi is " + dm.densityDpi); - if (DEBUG) println("display metrics: " + dm); - - //println("screen size is " + screenWidth + "x" + screenHeight); - -// LinearLayout layout = new LinearLayout(this); -// layout.setOrientation(LinearLayout.VERTICAL | LinearLayout.HORIZONTAL); -// viewGroup = new ViewGroup(); -// surfaceView.setLayoutParams(); -// viewGroup.setLayoutParams(LayoutParams.) -// RelativeLayout layout = new RelativeLayout(this); -// RelativeLayout overallLayout = new RelativeLayout(this); -// RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.FILL_PARENT); -//lp.addRule(RelativeLayout.RIGHT_OF, tv1.getId()); -// layout.setGravity(RelativeLayout.CENTER_IN_PARENT); - - int sw = sketchWidth(); - int sh = sketchHeight(); - - // Get renderer name and class - String rendererName = sketchRenderer(); - Class rendererClass = null; - try { - rendererClass = Class.forName(rendererName); - } catch (ClassNotFoundException exception) { - String message = String.format( - "Error: Could not resolve renderer class name: %s", rendererName); - throw new RuntimeException(message, exception); - } - - if (rendererName.equals(JAVA2D)) { - // JAVA2D renderer - surfaceView = new SketchSurfaceView(this, sw, sh, - (Class) rendererClass); - } else if (PGraphicsOpenGL.class.isAssignableFrom(rendererClass)) { - // P2D, P3D, and any other PGraphicsOpenGL-based renderer - surfaceView = new SketchSurfaceViewGL(this, sw, sh, - (Class) rendererClass); - } else { - // Anything else - String message = String.format( - "Error: Unsupported renderer class: %s", rendererName); - throw new RuntimeException(message); - } + } -// g = ((SketchSurfaceView) surfaceView).getGraphics(); -// surfaceView.setLayoutParams(new LayoutParams(sketchWidth(), sketchHeight())); + public PSurface getSurface() { + return surface; + } -// layout.addView(surfaceView); -// surfaceView.setVisibility(1); -// println("visibility " + surfaceView.getVisibility() + " " + SurfaceView.VISIBLE); -// layout.addView(surfaceView); -// AttributeSet as = new AttributeSet(); -// RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(layout, as); -// lp.addRule(android.R.styleable.ViewGroup_Layout_layout_height, -// layout.add - //lp.addRule(, arg1) - //layout.addView(surfaceView, sketchWidth(), sketchHeight()); + public Context getContext() { + return surface.getContext(); + } -// new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, -// RelativeLayout.LayoutParams.FILL_PARENT); - if (sw == displayWidth && sh == displayHeight) { - // If using the full screen, don't embed inside other layouts - window.setContentView(surfaceView); - } else { - // If not using full screen, setup awkward view-inside-a-view so that - // the sketch can be centered on screen. (If anyone has a more efficient - // way to do this, please file an issue on Google Code, otherwise you - // can keep your "talentless hack" comments to yourself. Ahem.) - RelativeLayout overallLayout = new RelativeLayout(this); - RelativeLayout.LayoutParams lp = - new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - lp.addRule(RelativeLayout.CENTER_IN_PARENT); - - LinearLayout layout = new LinearLayout(this); - layout.addView(surfaceView, sketchWidth(), sketchHeight()); - overallLayout.addView(layout, lp); - window.setContentView(overallLayout); - } + public Activity getActivity() { + return surface.getActivity(); + } - /* - // Here we use Honeycomb API (11+) to hide (in reality, just make the status icons into small dots) - // the status bar. Since the core is still built against API 7 (2.1), we use introspection to get - // the setSystemUiVisibility() method from the view class. - Method visibilityMethod = null; - try { - visibilityMethod = surfaceView.getClass().getMethod("setSystemUiVisibility", new Class[] { int.class}); - } catch (NoSuchMethodException e) { - // Nothing to do. This means that we are running with a version of Android previous to Honeycomb. - } - if (visibilityMethod != null) { - try { - // This is equivalent to calling: - //surfaceView.setSystemUiVisibility(View.STATUS_BAR_HIDDEN); - // The value of View.STATUS_BAR_HIDDEN is 1. - visibilityMethod.invoke(surfaceView, new Object[] { 1 }); - } catch (InvocationTargetException e) { - } catch (IllegalAccessException e) { - } - } - window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - */ + public void initSurface(AppComponent component, SurfaceHolder holder) { + parentLayout = -1; + initSurface(null, null, null, component, holder); + } -// layout.addView(surfaceView, lp); -// surfaceView.setLayoutParams(new LayoutParams(sketchWidth(), sketchHeight())); -// RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams() -// layout.addView(surfaceView, new LayoutParams(arg0) + public void initSurface(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState, + AppComponent component, SurfaceHolder holder) { + if (DEBUG) println("initSurface() happening here: " + Thread.currentThread().getName()); - // TODO probably don't want to set these here, can't we wait for surfaceChanged()? - // removing this in 0187 -// width = screenWidth; -// height = screenHeight; + component.initDimensions(); + displayWidth = component.getDisplayWidth(); + displayHeight = component.getDisplayHeight(); + displayDensity = component.getDisplayDensity(); -// int left = (screenWidth - iwidth) / 2; -// int right = screenWidth - (left + iwidth); -// int top = (screenHeight - iheight) / 2; -// int bottom = screenHeight - (top + iheight); -// surfaceView.setPadding(left, top, right, bottom); - // android:layout_width + handleSettings(); -// window.setContentView(surfaceView); // set full screen + boolean parentSize = false; + if (parentLayout == -1) { + if (fullScreen || width == -1 || height == -1) { + // Either sketch explicitly set to full-screen mode, or not + // size/fullScreen provided, so sketch uses the entire display + width = displayWidth; + height = displayHeight; + } + } else { + if (fullScreen || width == -1 || height == -1) { + // Dummy weight and height to initialize the PGraphics, will be resized + // when the view associated to the parent layout is created + width = 100; + height = 100; + parentSize = true; + } + } - // code below here formerly from init() + pixelWidth = width * pixelDensity; + pixelHeight = height * pixelDensity; - //millisOffset = System.currentTimeMillis(); // moved to the variable declaration + String rendererName = sketchRenderer(); + if (DEBUG) println("Renderer " + rendererName); + g = makeGraphics(width, height, rendererName, true); + if (DEBUG) println("Created renderer"); + surface = g.createSurface(component, holder, false); + if (DEBUG) println("Created surface"); + + if (parentLayout == -1) { + setFullScreenVisibility(); + surface.initView(width, height); + } else { + surface.initView(width, height, parentSize, + inflater, container, savedInstanceState); + } finished = false; // just for clarity - // this will be cleared by draw() if it is not overridden looping = true; redraw = true; // draw this guy once -// firstMotion = true; - Context context = getApplicationContext(); - sketchPath = context.getFilesDir().getAbsolutePath(); + sketchPath = surface.getFilesDir().getAbsolutePath(); -// Looper.prepare(); - handler = new Handler(); -// println("calling loop()"); -// Looper.loop(); -// println("done with loop() call, will continue..."); + surface.startThread(); - start(); + if (DEBUG) println("Done with init surface"); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - if (DEBUG) System.out.println("configuration changed: " + newConfig); - super.onConfigurationChanged(newConfig); + private void setFullScreenVisibility() { + if (fullScreen) { + runOnUiThread(new Runnable() { + @Override + public void run() { + int visibility; + if (SDK < 19) { + // Pre-4.4 + visibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } else { + // 4.4 and higher. Integer instead of constants defined in View so it can + // build with SDK < 4.4 + visibility = 256 | // View.SYSTEM_UI_FLAG_LAYOUT_STABLE + 512 | // View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + 1024 | // View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | + 4 | // View.SYSTEM_UI_FLAG_FULLSCREEN + 4096; // View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + // However, this visibility does not fix a bug where the navigation area + // turns black after resuming the app: + // https://code.google.com/p/android/issues/detail?id=170752 + } + surface.setSystemUiVisibility(visibility); + } + }); + } } - @Override - protected void onResume() { - super.onResume(); - - // TODO need to bring back app state here! -// surfaceView.onResume(); + public void onResume() { if (DEBUG) System.out.println("PApplet.onResume() called"); - paused = false; + if (parentLayout == -1) { + setFullScreenVisibility(); + } + handleMethods("resume"); - //start(); // kick the thread back on - resume(); -// surfaceView.onResume(); + + // Don't call resume() when the app is starting and setup() has not been called yet: + // https://github.com/processing/processing-android/issues/274 + // Also, there is no need to call resume() from anywhere else (for example, from + // onStart) since onResume() is always called in the activity lifecyle: + // https://developer.android.com/guide/components/activities/activity-lifecycle.html + if (0 < frameCount) { + resume(); + } + + // Set the handledBackPressed to true to handle the situation where a fragment is popping + // right back after pressing the back button (the sketch does not exit). + handledBackPressed = true; + + if (g != null) { + g.restoreState(); + } + + surface.resumeThread(); } - @Override - protected void onPause() { - super.onPause(); + public void onPause() { + surface.pauseThread(); + + // Make sure that the keyboard is not left open after leaving the app + closeKeyboard(); + + if (g != null) { + g.saveState(); + } - // TODO need to save all application state here! -// System.out.println("PApplet.onPause() called"); - paused = true; handleMethods("pause"); + pause(); // handler for others to write -// synchronized (this) { -// paused = true; -//} -// surfaceView.onPause(); } - /** - * Developers can override here to save state. The 'paused' variable will be - * set before this function is called. - */ - public void pause() { + public void onStart() { + start(); } - /** - * Developers can override here to restore state. The 'paused' variable - * will be cleared before this function is called. - */ - public void resume() { + public void onStop() { + stop(); + } + + + public void onCreate(Bundle savedInstanceState) { + create(); } - @Override public void onDestroy() { -// stop(); + handleMethods("onDestroy"); + + surface.stopThread(); + dispose(); - if (PApplet.DEBUG) { - System.out.println("PApplet.onDestroy() called"); - } - super.onDestroy(); - //finish(); } + public void onActivityResult(int requestCode, int resultCode, Intent data) { + handleMethods("onActivityResult", new Object[] { requestCode, resultCode, data }); + } - ////////////////////////////////////////////////////////////// - // ANDROID SURFACE VIEW + public void onNewIntent(Intent intent) { + handleMethods("onNewIntent", new Object[] { intent }); + } + + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ - // TODO this is only used by A2D, when finishing up a draw. but if the - // surfaceview has changed, then it might belong to an a3d surfaceview. hrm. - public SurfaceHolder getSurfaceHolder() { - return surfaceView.getHolder(); -// return surfaceHolder; } - /** Not official API, not guaranteed to work in the future. */ - public SurfaceView getSurfaceView() { - return surfaceView; + public boolean onOptionsItemSelected(MenuItem item) { + return false; } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + } -// public interface SketchSurfaceView { -// public PGraphics getGraphics(); -// } + public boolean onContextItemSelected(MenuItem item) { + return false; + } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + public void setHasOptionsMenu(boolean hasMenu) { + surface.setHasOptionsMenu(hasMenu); + } - public class SketchSurfaceView extends SurfaceView implements - SurfaceHolder.Callback { - PGraphicsAndroid2D g2; - SurfaceHolder surfaceHolder; + synchronized public void onBackPressed() { + requestedBackPress = true; + } - public SketchSurfaceView(Context context, int wide, int high, - Class clazz) { - super(context); + public FragmentManager getFragmentManager() { + if (getActivity() != null) { + return getActivity().getFragmentManager(); + } + return null; + } -// println("surface holder"); - // Install a SurfaceHolder.Callback so we get notified when the - // underlying surface is created and destroyed - surfaceHolder = getHolder(); - surfaceHolder.addCallback(this); - surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU); -// println("creating graphics"); - if (clazz.equals(PGraphicsAndroid2D.class)) { - g2 = new PGraphicsAndroid2D(); - } else { - try { - Constructor constructor = - clazz.getConstructor(); - g2 = constructor.newInstance(); - } catch (Exception exception) { - throw new RuntimeException( - "Error: Failed to initialize custom Android2D renderer", - exception); - } - } + public Window getWindow(){ + if (getActivity() != null) { + return getActivity().getWindow(); + } + return null; + } - // Set semi-arbitrary size; will be set properly when surfaceChanged() called - g2.setSize(wide, high); -// newGraphics.setSize(getWidth(), getHeight()); - g2.setParent(PApplet.this); - g2.setPrimary(true); - // Set the value for 'g' once everything is ready (otherwise rendering - // may attempt before setSize(), setParent() etc) -// g = newGraphics; - g = g2; // assign the g object for the PApplet -// println("setting focusable, requesting focus"); - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); -// println("done making surface view"); - } + public void startActivity(Intent intent) { + surface.startActivity(intent); + } -// public PGraphics getGraphics() { -// return g2; -// } + public void runOnUiThread(Runnable action) { + surface.runOnUiThread(action); + } - // part of SurfaceHolder.Callback - public void surfaceCreated(SurfaceHolder holder) { - } + public boolean hasPermission(String permission) { + return surface.hasPermission(permission); + } - // part of SurfaceHolder.Callback - public void surfaceDestroyed(SurfaceHolder holder) { - //g2.dispose(); + public void requestPermission(String permission) { + if (!hasPermission(permission)) { + reqPermissions.add(permission); } + } - // part of SurfaceHolder.Callback - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - if (DEBUG) { - System.out.println("SketchSurfaceView2D.surfaceChanged() " + w + " " + h); - } - surfaceChanged = true; + public void requestPermission(String permission, String callback) { + requestPermission(permission, callback, this); + } -// width = w; -// height = h; -// -// g.setSize(w, h); + + public void requestPermission(String permission, String callback, Object target) { + registerWithArgs(callback, target, new Class[] { boolean.class }); + if (hasPermission(permission)) { + // If the app already has permission, still call the handle method as it + // may be doing some initialization + handleMethods(callback, new Object[] { true }); + } else { + permissionMethods.put(permission, callback); + // Accumulating permissions so they requested all at once at the end + // of draw. + reqPermissions.add(permission); } + } - @Override - public void onWindowFocusChanged(boolean hasFocus) { - surfaceWindowFocusChanged(hasFocus); + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults) { + if (requestCode == PSurface.REQUEST_PERMISSIONS) { + for (int i = 0; i < grantResults.length; i++) { + boolean granted = grantResults[i] == PackageManager.PERMISSION_GRANTED; + handlePermissionsResult(permissions[i], granted); + } } + } - @Override - public boolean onTouchEvent(MotionEvent event) { - return surfaceTouchEvent(event); + private void handlePermissionsResult(String permission, final boolean granted) { + String methodName = permissionMethods.get(permission); + final RegisteredMethods meth = registerMap.get(methodName); + if (meth != null) { + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + meth.handle(new Object[] { granted }); + } + }); } + } - @Override - public boolean onKeyDown(int code, android.view.KeyEvent event) { - return surfaceKeyDown(code, event); + private void handlePermissions() { + if (0 < reqPermissions.size()) { + String[] req = reqPermissions.toArray(new String[reqPermissions.size()]); + surface.requestPermissions(req); + reqPermissions.clear(); } + } + synchronized private void handleBackPressed() { + if (requestedBackPress) { + requestedBackPress = false; + backPressed(); + if (!handledBackPressed) { + if (getActivity() != null) { + // Services don't have an activity associated to them, but back press could not be triggered for those anyways + getActivity().finish(); + } + handledBackPressed = false; + } + } + } - @Override - public boolean onKeyUp(int code, android.view.KeyEvent event) { - return surfaceKeyUp(code, event); + /** + * @param method "size" or "fullScreen" + * @param args parameters passed to the function so we can show the user + * @return true if safely inside the settings() method + */ + boolean insideSettings(String method, Object... args) { + if (insideSettings) { + return true; + } + final String url = "https://processing.org/reference/" + method + "_.html"; + if (!external) { // post a warning for users of Eclipse and other IDEs + StringList argList = new StringList(args); + System.err.println("When not using the PDE, " + method + "() can only be used inside settings()."); + System.err.println("Remove the " + method + "() method from setup(), and add the following:"); + System.err.println("public void settings() {"); + System.err.println(" " + method + "(" + argList.join(", ") + ");"); + System.err.println("}"); } + throw new IllegalStateException(method + "() cannot be used here, see " + url); + } - // don't think i want to call stop() from here, since it might be swapping renderers -// @Override -// protected void onDetachedFromWindow() { -// super.onDetachedFromWindow(); -// stop(); -// } + void handleSettings() { + insideSettings = true; + //Do stuff + settings(); + insideSettings = false; } - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + public void settings() { + //It'll be empty. Will be overridden by user's sketch class. + } - public class SketchSurfaceViewGL extends GLSurfaceView { - PGraphicsOpenGL g3; - SurfaceHolder surfaceHolder; + final public int sketchWidth() { + return width; + } - public SketchSurfaceViewGL(Context context, int wide, int high, - Class clazz) { - super(context); + final public int sketchHeight() { + return height; + } - // Check if the system supports OpenGL ES 2.0. - final ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo(); - final boolean supportsGLES2 = configurationInfo.reqGlEsVersion >= 0x20000; - if (!supportsGLES2) { - throw new RuntimeException("OpenGL ES 2.0 is not supported by this device."); - } + final public String sketchRenderer() { + return renderer; + } - surfaceHolder = getHolder(); - // are these two needed? - surfaceHolder.addCallback(this); - //surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_GPU); - - // The PGraphics object needs to be created here so the renderer is not - // null. This is required because PApplet.onResume events (which call - // this.onResume() and thus require a valid renderer) are triggered - // before surfaceChanged() is ever called. - - if (clazz.equals(PGraphics2D.class)) { // P2D - g3 = new PGraphics2D(); - } else if (clazz.equals(PGraphics3D.class)) { // P3D - g3 = new PGraphics3D(); - } else { // something that extends P2D, P3D, or PGraphicsOpenGL - try { - Constructor constructor = - clazz.getConstructor(); - g3 = constructor.newInstance(); - } catch (Exception exception) { - throw new RuntimeException( - "Error: Failed to initialize custom OpenGL renderer", - exception); - } - } - //set it up - g3.setParent(PApplet.this); - g3.setPrimary(true); - // Set semi-arbitrary size; will be set properly when surfaceChanged() called - g3.setSize(wide, high); + public int sketchSmooth() { + return smooth; + } - // Tells the default EGLContextFactory and EGLConfigChooser to create an GLES2 context. - setEGLContextClientVersion(2); - int quality = sketchQuality(); - if (1 < quality) { - setEGLConfigChooser(((PGLES)g3.pgl).getConfigChooser(quality)); - } + final public boolean sketchFullScreen() { + return fullScreen; + } - // The renderer can be set only once. - setRenderer(((PGLES)g3.pgl).getRenderer()); - setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); - // assign this g to the PApplet - g = g3; + final public int sketchDisplay() { + return display; + } - setFocusable(true); - setFocusableInTouchMode(true); - requestFocus(); - } + final public String sketchOutputPath() { + return null; + } - public PGraphics getGraphics() { - return g3; - } + final public OutputStream sketchOutputStream() { + return null; + } - // part of SurfaceHolder.Callback - @Override - public void surfaceCreated(SurfaceHolder holder) { - super.surfaceCreated(holder); - if (DEBUG) { - System.out.println("surfaceCreated()"); - } - } + final public int sketchWindowColor() { + return windowColor; + } - // part of SurfaceHolder.Callback - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - super.surfaceDestroyed(holder); - if (DEBUG) { - System.out.println("surfaceDestroyed()"); - } - /* - // TODO: Check how to make sure of calling g3.dispose() when this call to - // surfaceDestoryed corresponds to the sketch being shut down instead of just - // taken to the background. + final public int sketchPixelDensity() { + return pixelDensity; + } - // For instance, something like this would be ok? - // The sketch is being stopped, so we dispose the resources. - if (!paused) { - g3.dispose(); - } - */ - } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - super.surfaceChanged(holder, format, w, h); - if (DEBUG) { - System.out.println("SketchSurfaceView3D.surfaceChanged() " + w + " " + h); - } - surfaceChanged = true; -// width = w; -// height = h; -// g.setSize(w, h); + public void surfaceChanged() { + surfaceChanged = true; + g.surfaceChanged(); + } - // No need to call g.setSize(width, height) b/c super.surfaceChanged() - // will trigger onSurfaceChanged in the renderer, which calls setSize(). - // -- apparently not true? (100110) - } - - /** - * Inform the view that the window focus has changed. - */ - @Override - public void onWindowFocusChanged(boolean hasFocus) { - surfaceWindowFocusChanged(hasFocus); -// super.onWindowFocusChanged(hasFocus); -// focused = hasFocus; -// if (focused) { -//// println("got focus"); -// focusGained(); -// } else { -//// println("lost focus"); -// focusLost(); -// } - } - - - @Override - public boolean onTouchEvent(MotionEvent event) { - return surfaceTouchEvent(event); - } - - - @Override - public boolean onKeyDown(int code, android.view.KeyEvent event) { - return surfaceKeyDown(code, event); - } - - - @Override - public boolean onKeyUp(int code, android.view.KeyEvent event) { - return surfaceKeyUp(code, event); - } - - - // don't think i want to call stop() from here, since it might be swapping renderers -// @Override -// protected void onDetachedFromWindow() { -// super.onDetachedFromWindow(); -// stop(); -// } - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . /** @@ -995,7 +906,6 @@ public boolean onKeyUp(int code, android.view.KeyEvent event) { * by Android as well. */ public void surfaceWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); focused = hasFocus; if (focused) { focusGained(); @@ -1006,69 +916,25 @@ public void surfaceWindowFocusChanged(boolean hasFocus) { /** - * If you override this function without calling super.onTouchEvent(), + * If you override this function without calling super.surfaceTouchEvent(), * then motionX, motionY, motionPressed, and motionEvent will not be set. */ public boolean surfaceTouchEvent(MotionEvent event) { -// println(event); nativeMotionEvent(event); -// return super.onTouchEvent(event); return true; } - public boolean surfaceKeyDown(int code, android.view.KeyEvent event) { - // System.out.println("got onKeyDown for " + code + " " + event); + public void surfaceKeyDown(int code, android.view.KeyEvent event) { nativeKeyEvent(event); - return super.onKeyDown(code, event); } - public boolean surfaceKeyUp(int code, android.view.KeyEvent event) { - // System.out.println("got onKeyUp for " + code + " " + event); + public void surfaceKeyUp(int code, android.view.KeyEvent event) { nativeKeyEvent(event); - return super.onKeyUp(code, event); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - public int sketchQuality() { - return 1; } - public int sketchWidth() { - return displayWidth; - } - - - public int sketchHeight() { - return displayHeight; - } - - - public String sketchRenderer() { - return JAVA2D; - } - - - public void orientation(int which) { - if (which == PORTRAIT) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - } else if (which == LANDSCAPE) { - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); - } - } - - -// public int sketchOrientation() { -// return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; -// //setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); -// } - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1081,13 +947,6 @@ public void orientation(int which) { * PAppletGL needs to have a usable screen before getting things rolling. */ public void start() { - finished = false; - paused = false; // unpause the thread - - if (thread == null) { - thread = new Thread(this, "Animation Thread"); - thread.start(); - } } @@ -1100,48 +959,28 @@ public void start() { * or when moving between web pages), and it's not always called. */ public void stop() { - // this used to shut down the sketch, but that code has - // been moved to dispose() - - paused = true; // sleep the animation thread - - //TODO listeners } /** - * Called by the browser or applet viewer to inform this applet - * that it is being reclaimed and that it should destroy - * any resources that it has allocated. - *

- * This also attempts to call PApplet.stop(), in case there - * was an inadvertent override of the stop() function by a user. - *

- * destroy() supposedly gets called as the applet viewer - * is shutting down the applet. stop() is called - * first, and then destroy() to really get rid of things. - * no guarantees on when they're run (on browser quit, or - * when moving between pages), though. + * Developers can override here to save state. The 'paused' variable will be + * set before this function is called. */ - public void destroy() { - ((PApplet)this).exit(); + public void pause() { } /** - * This returns the last width and height specified by the user - * via the size() command. + * Developers can override here to restore state. The 'paused' variable + * will be cleared before this function is called. */ -// public Dimension getPreferredSize() { -// return new Dimension(width, height); -// } - + public void resume() { + } -// public void addNotify() { -// super.addNotify(); -// println("addNotify()"); -// } + public void backPressed() { + handledBackPressed = false; + } ////////////////////////////////////////////////////////////// @@ -1258,8 +1097,8 @@ protected int findIndex(Object object) { *

  • pre – at the very top of the draw() method (safe to draw) *
  • draw – at the end of the draw() method (safe to draw) *
  • post – after draw() has exited (not safe to draw) - *
  • pause – called when the sketch is paused - *
  • resume – called when the sketch is resumed + *
  • pause – called when the sketch is paused + *
  • resume – called when the sketch is resumed *
  • dispose – when the sketch is shutting down (definitely not safe to draw) *
      * In addition, the new (for 2.0) processing.event classes are passed to @@ -1284,6 +1123,14 @@ public void registerMethod(String methodName, Object target) { } else if (methodName.equals("touchEvent")) { registerWithArgs("touchEvent", target, new Class[] { processing.event.TouchEvent.class }); + // Android-lifecycle event handlers + } else if (methodName.equals("onDestroy")) { + registerNoArgs(methodName, target); + } else if (methodName.equals("onActivityResult")) { + registerWithArgs("onActivityResult", target, new Class[] { int.class, int.class, Intent.class }); + } else if (methodName.equals("onNewIntent")) { + registerWithArgs("onNewIntent", target, new Class[] { Intent.class }); + } else { registerNoArgs(methodName, target); } @@ -1360,10 +1207,16 @@ protected void handleMethods(String methodName) { } - protected void handleMethods(String methodName, Object[] args) { - RegisteredMethods meth = registerMap.get(methodName); + protected void handleMethods(String methodName, final Object[] args) { + final RegisteredMethods meth = registerMap.get(methodName); if (meth != null) { - meth.handle(args); + Handler handler = new Handler(Looper.getMainLooper()); + handler.post(new Runnable() { + @Override + public void run() { + meth.handle(args); + } + }); } } @@ -1467,6 +1320,54 @@ public void draw() { // } + /** + * Create a full-screen sketch using the default renderer. + */ + public void fullScreen() { + if (!fullScreen) { + if (insideSettings("fullScreen")) { + this.fullScreen = true; + } + } + } + + + public void fullScreen(int display) { + //Display index doesn't make sense in Android. + //Should we throw some error in log ? + if (!fullScreen /*|| display != this.display*/) { + if (insideSettings("fullScreen", display)) { + this.fullScreen = true; +// this.display = display; + } + } + } + + + public void fullScreen(String renderer) { + if (!fullScreen || + !renderer.equals(this.renderer)) { + if (insideSettings("fullScreen", renderer)) { + this.fullScreen = true; + this.renderer = renderer; + } + } + } + + + public void fullScreen(String renderer, int display) { + if (!fullScreen || + !renderer.equals(this.renderer) /*|| + display != this.display*/) { + if (insideSettings("fullScreen", renderer, display)) { + this.fullScreen = true; + this.renderer = renderer; +// this.display = display; + } + } + } + + /** * Starts up and creates a two-dimensional drawing surface, or resizes the * current drawing surface. @@ -1477,15 +1378,92 @@ public void draw() { * previous renderer and simply resize it. */ public void size(int iwidth, int iheight) { - size(iwidth, iheight, P2D, null); + if (iwidth != this.width || iheight != this.height) { + if (insideSettings("size", iwidth, iheight)) { + this.width = iwidth; + this.height = iheight; + } + } } public void size(int iwidth, int iheight, String irenderer) { - size(iwidth, iheight, irenderer, null); + if (iwidth != this.width || iheight != this.height || + !this.renderer.equals(irenderer)) { + if (insideSettings("size", iwidth, iheight, irenderer)) { + this.width = iwidth; + this.height = iheight; + this.renderer = irenderer; + } + } } + public void setSize(int width, int height) { + if (fullScreen) { + this.displayWidth = width; + this.displayHeight = height; + } + this.width = width; + this.height = height; + pixelWidth = width * pixelDensity; + pixelHeight = height * pixelDensity; + g.setSize(sketchWidth(), sketchHeight()); + } + + + public void setExternal(boolean external) { + this.external = external; + } + + +//. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public void smooth() { + smooth(1); + } + + + public void smooth(int level) { + if (insideSettings) { + this.smooth = level; + } else if (this.smooth != level) { + smoothWarning("smooth"); + } + } + + + public void noSmooth() { + if (insideSettings) { + this.smooth = 0; + } else if (this.smooth != 0) { + smoothWarning("noSmooth"); + } + } + + private void smoothWarning(String method) { + // When running from the PDE, say setup(), otherwise say settings() + final String where = external ? "setup" : "settings"; + PGraphics.showWarning("%s() can only be used inside %s()", method, where); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public PGraphics getGraphics() { + return g; + } + + public void orientation(int which) { + surface.setOrientation(which); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + // not finished yet--will swap the renderer at a bad time /* public void renderer(String name) { @@ -1519,82 +1497,15 @@ public void renderer(String name) { */ public void size(final int iwidth, final int iheight, final String irenderer, final String ipath) { - System.out.println("This size() method is ignored on Android."); - System.out.println("See http://wiki.processing.org/w/Android for more information."); - - /* -// Looper.prepare(); - // Run this from the EDT, just cuz it's AWT stuff (or maybe later Swing) -// new Handler().post(new Runnable() { - handler.post(new Runnable() { - public void run() { - println("Handler is on thread " + Thread.currentThread().getName()); -// // Set the preferred size so that the layout managers can handle it -//// setPreferredSize(new Dimension(iwidth, iheight)); -//// setSize(iwidth, iheight); -// g.setSize(iwidth, iheight); -// } -// }); - - // ensure that this is an absolute path -// if (ipath != null) ipath = savePath(ipath); - // no path renderers supported yet - - println("I'm the ole thread " + Thread.currentThread().getName()); - String currentRenderer = g.getClass().getName(); - if (currentRenderer.equals(irenderer)) { - println("resizing renderer inside size(....)"); - // Avoid infinite loop of throwing exception to reset renderer - resizeRenderer(iwidth, iheight); - //redraw(); // will only be called insize draw() - - } else { // renderer is being changed - println("changing renderer inside size(....)"); - - // otherwise ok to fall through and create renderer below - // the renderer is changing, so need to create a new object -// g = makeGraphics(iwidth, iheight, irenderer, ipath, true); -// width = iwidth; -// height = iheight; -// if () - - // Remove the old view from the layout - layout.removeView(surfaceView); - - if (irenderer.equals(A2D)) { - surfaceView = new SketchSurfaceView2D(PApplet.this, iwidth, iheight); - } else if (irenderer.equals(A3D)) { - surfaceView = new SketchSurfaceView3D(PApplet.this, iwidth, iheight); + if (iwidth != this.width || iheight != this.height || + !this.renderer.equals(irenderer)) { + if (insideSettings("size", iwidth, iheight, irenderer, + ipath)) { + this.width = iwidth; + this.height = iheight; + this.renderer = irenderer; } - g = ((SketchSurfaceView) surfaceView).getGraphics(); - - // these don't seem like a good idea -// width = screenWidth; -// height = screenHeight; - -// println("getting window"); -// Window window = getWindow(); -// window.setContentView(surfaceView); // set full screen -// layout.removeAllViews(); - layout.addView(surfaceView); - - // fire resize event to make sure the applet is the proper size -// setSize(iwidth, iheight); - // this is the function that will run if the user does their own - // size() command inside setup, so set defaultSize to false. -// defaultSize = false; - - // throw an exception so that setup() is called again - // but with a properly sized render - // this is for opengl, which needs a valid, properly sized - // display before calling anything inside setup(). -// throw new RendererChangeException(); - println("interrupting animation thread"); - thread.interrupt(); } - } - }); -*/ } @@ -1654,16 +1565,23 @@ public PGraphics createGraphics(int iwidth, int iheight) { *
    */ public PGraphics createGraphics(int iwidth, int iheight, String irenderer) { + return makeGraphics(iwidth, iheight, irenderer, false); + } + + + protected PGraphics makeGraphics(int w, int h, + String renderer, boolean primary) { PGraphics pg = null; - if (irenderer.equals(JAVA2D)) { + if (renderer.equals(JAVA2D)) { pg = new PGraphicsAndroid2D(); - } else if (irenderer.equals(P2D)) { - if (!g.isGL()) { + } else if (renderer.equals(P2D)) { + if (!primary && !g.isGL()) { throw new RuntimeException("createGraphics() with P2D requires size() to use P2D or P3D"); } pg = new PGraphics2D(); - } else if (irenderer.equals(P3D)) { - if (!g.isGL()) { + + } else if (renderer.equals(P3D)) { + if (!primary && !g.isGL()) { throw new RuntimeException("createGraphics() with P3D or OPENGL requires size() to use P2D or P3D"); } pg = new PGraphics3D(); @@ -1671,12 +1589,8 @@ public PGraphics createGraphics(int iwidth, int iheight, String irenderer) { Class rendererClass = null; Constructor constructor = null; try { - // The context class loader doesn't work: - //rendererClass = Thread.currentThread().getContextClassLoader().loadClass(irenderer); - // even though it should, according to this discussion: // http://code.google.com/p/android/issues/detail?id=11101 - // While the method that is not supposed to work, using the class loader, does: - rendererClass = this.getClass().getClassLoader().loadClass(irenderer); + rendererClass = Thread.currentThread().getContextClassLoader().loadClass(renderer); } catch (ClassNotFoundException cnfe) { throw new RuntimeException("Missing renderer class"); } @@ -1692,27 +1606,28 @@ public PGraphics createGraphics(int iwidth, int iheight, String irenderer) { try { pg = (PGraphics) constructor.newInstance(); } catch (InvocationTargetException e) { - e.printStackTrace(); + printStackTrace(e); throw new RuntimeException(e.getMessage()); } catch (IllegalAccessException e) { - e.printStackTrace(); + printStackTrace(e); throw new RuntimeException(e.getMessage()); } catch (InstantiationException e) { - e.printStackTrace(); + printStackTrace(e); throw new RuntimeException(e.getMessage()); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + printStackTrace(e); } } } } pg.setParent(this); - pg.setPrimary(false); - pg.setSize(iwidth, iheight); - + pg.setPrimary(primary); + pg.setSize(w, h); return pg; } - /** * Create an offscreen graphics surface for drawing, in this case * for a renderer that writes to a file (such as PDF or DXF). @@ -1860,9 +1775,7 @@ protected void paint() { ////////////////////////////////////////////////////////////// - /** - * Main method for the primary animation thread. - */ +/* public void run() { // not good to make this synchronized, locks things up long beforeTime = System.nanoTime(); long overSleepTime = 0L; @@ -1872,7 +1785,7 @@ public void run() { // not good to make this synchronized, locks things up // animation thread yields to other running threads. final int NO_DELAYS_PER_YIELD = 15; - while ((Thread.currentThread() == thread) && !finished) { + while (!finished) { while (paused) { try{ @@ -1882,27 +1795,8 @@ public void run() { // not good to make this synchronized, locks things up } } - // Don't resize the renderer from the EDT (i.e. from a ComponentEvent), - // otherwise it may attempt a resize mid-render. -// if (resizeRequest) { -// resizeRenderer(resizeWidth, resizeHeight); -// resizeRequest = false; -// } - // render a single frame if (g != null) g.requestDraw(); -// g.requestDraw(); -// surfaceView.requestDraw(); - - // removed in android -// if (frameCount == 1) { -// // Call the request focus event once the image is sure to be on -// // screen and the component is valid. The OpenGL renderer will -// // request focus for its canvas inside beginDraw(). -// // http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html -// //println("requesting focus"); -// requestFocus(); -// } // wait for update & paint to happen before drawing next frame // this is necessary since the drawing is sometimes in a @@ -1944,132 +1838,134 @@ public void run() { // not good to make this synchronized, locks things up // If the user called the exit() function, the window should close, // rather than the sketch just halting. if (exitCalled) { - exit2(); + exitActual(); } } } - +*/ public void handleDraw() { - if (DEBUG) { - println("inside handleDraw() " + millis() + - " changed=" + surfaceChanged + - " ready=" + surfaceReady + - " paused=" + paused + - " looping=" + looping + - " redraw=" + redraw); - } - if (surfaceChanged) { - int newWidth = surfaceView.getWidth(); - int newHeight = surfaceView.getHeight(); - if (newWidth != width || newHeight != height) { - width = newWidth; - height = newHeight; - g.setSize(width, height); - } - surfaceChanged = false; - surfaceReady = true; - if (DEBUG) { - println("surfaceChanged true, resized to " + width + "x" + height); - } + //debug("handleDraw() " + g + " " + looping + " " + redraw + " valid:" + this.isValid() + " visible:" + this.isVisible()); + + if (g == null) return; + + if (!surfaceChanged && parentLayout != -1) { + // When using a parent layout, don't start drawing until the sketch + // has been properly sized. + return; } -// if (surfaceView.isShown()) { -// println("surface view not visible, getting out"); -// return; -// } else { -// println("surface set to go."); -// } + if (!looping && !redraw) return; - // don't start drawing (e.g. don't call setup) until there's a legitimate - // width and height that have been set by surfaceChanged(). -// boolean validSize = width != 0 && height != 0; -// println("valid size = " + validSize + " (" + width + "x" + height + ")"); - if (canDraw()) { -// if (!g.canDraw()) { -// // Don't draw if the renderer is not yet ready. -// // (e.g. OpenGL has to wait for a peer to be on screen) -// return; -// } + if (insideDraw) { + System.err.println("handleDraw() called before finishing"); + System.exit(1); + } - g.beginDraw(); + insideDraw = true; - long now = System.nanoTime(); +// if (recorder != null) { +// recorder.beginDraw(); +// } - if (frameCount == 0) { - try { - //println("Calling setup()"); - setup(); - //println("Done with setup()"); + if (handleSpecialDraw()) return; - } catch (RendererChangeException e) { - // Give up, instead set the new renderer and re-attempt setup() - return; - } -// this.defaultSize = false; + g.beginDraw(); - } else { // frameCount > 0, meaning an actual draw() - // update the current frameRate - double rate = 1000000.0 / ((now - frameRateLastNanos) / 1000000.0); - float instantaneousRate = (float) rate / 1000.0f; - frameRate = (frameRate * 0.9f) + (instantaneousRate * 0.1f); + long now = System.nanoTime(); - if (frameCount != 0) { - handleMethods("pre"); - } + if (frameCount == 0) { + setup(); - // use dmouseX/Y as previous mouse pos, since this is the - // last position the mouse was in during the previous draw. - pmouseX = dmouseX; - pmouseY = dmouseY; -// pmotionX = dmotionX; -// pmotionY = dmotionY; - - //println("Calling draw()"); - draw(); - //println("Done calling draw()"); - - // dmouseX/Y is updated only once per frame (unlike emouseX/Y) - dmouseX = mouseX; - dmouseY = mouseY; -// dmotionX = motionX; -// dmotionY = motionY; - - // these are called *after* loop so that valid - // drawing commands can be run inside them. it can't - // be before, since a call to background() would wipe - // out anything that had been drawn so far. -// dequeueMotionEvents(); -// dequeueKeyEvents(); - dequeueEvents(); - - handleMethods("draw"); - - redraw = false; // unset 'redraw' flag in case it was set - // (only do this once draw() has run, not just setup()) - } - g.endDraw(); + } else { // frameCount > 0, meaning an actual draw() + // update the current frameRate + double rate = 1000000.0 / ((now - frameRateLastNanos) / 1000000.0); + float instantaneousRate = (float) (rate / 1000.0); + frameRate = (frameRate * 0.9f) + (instantaneousRate * 0.1f); if (frameCount != 0) { - handleMethods("post"); + handleMethods("pre"); } - frameRateLastNanos = now; - frameCount++; + // use dmouseX/Y as previous mouse pos, since this is the + // last position the mouse was in during the previous draw. + pmouseX = dmouseX; + pmouseY = dmouseY; + + draw(); + + // dmouseX/Y is updated only once per frame (unlike emouseX/Y) + dmouseX = mouseX; + dmouseY = mouseY; + + // these are called *after* loop so that valid + // drawing commands can be run inside them. it can't + // be before, since a call to background() would wipe + // out anything that had been drawn so far. + dequeueEvents(); + + handleMethods("draw"); + handlePermissions(); + handleBackPressed(); + + redraw = false; // unset 'redraw' flag in case it was set + // (only do this once draw() has run, not just setup()) + } + g.endDraw(); + +// if (recorder != null) { +// recorder.endDraw(); +// } + insideDraw = false; + + if (frameCount != 0) { + handleMethods("post"); } + + frameRateLastNanos = now; + frameCount++; } - /** Not official API, not guaranteed to work in the future. */ - public boolean canDraw() { - return g != null && surfaceReady && !paused && (looping || redraw); + // This method handles some special situations on Android where beginDraw/endDraw are needed, + // but not to render the actual contents of draw(). In general, these situations arise from + // having to refresh/restore the screen after requesting no loop, or resuming the sketch in + // no-loop state. + protected boolean handleSpecialDraw() { + boolean handled = false; + + if (g.restoringState()) { + // The sketch is restoring, so begin/end the frame properly and quit drawing. + g.beginDraw(); + g.endDraw(); + + handled = true; + } else if (g.requestedNoLoop) { + // noLoop() was called sometime in the previous frame with a GL renderer, but only now + // we are sure that the frame is properly displayed. + looping = false; + + // Perform a full frame draw, to ensure that the previous frame is properly displayed (see + // comment in the declaration of requestedNoLoop). + g.beginDraw(); + g.endDraw(); + + g.requestedNoLoop = false; + handled = true; + } + + if (handled) { + insideDraw = false; + return true; + } else { + return false; + } } ////////////////////////////////////////////////////////////// - synchronized public void redraw() { if (!looping) { redraw = true; @@ -2097,11 +1993,20 @@ synchronized public void loop() { synchronized public void noLoop() { if (looping) { - looping = false; + if (g.requestNoLoop()) { + g.requestedNoLoop = true; + } else { + looping = false; + } } } + public boolean isLooping() { + return looping; + } + + ////////////////////////////////////////////////////////////// @@ -2179,9 +2084,9 @@ protected void dequeueEvents() { Event e = eventQueue.remove(); switch (e.getFlavor()) { -// case Event.TOUCH: -// handleTouchEvent((TouchEvent) e); -// break; + case Event.TOUCH: + handleTouchEvent((TouchEvent) e); + break; case Event.MOUSE: handleMouseEvent((MouseEvent) e); break; @@ -2228,7 +2133,7 @@ protected void handleMouseEvent(MouseEvent event) { // } // Get the (already processed) button code -// mouseButton = event.getButton(); + mouseButton = event.getButton(); // Added in 0215 (2.0b7) so that pmouseX/Y behave more like one would // expect from the desktop. This makes the ContinousLines example behave. @@ -2302,377 +2207,192 @@ protected void handleMouseEvent(MouseEvent event) { } - /* - // ******************** NOT CURRENTLY IN USE ******************** - // this class is hiding inside PApplet for now, - // until I have a chance to get the API right inside TouchEvent. - class AndroidTouchEvent extends TouchEvent { - int action; - int numPointers; - float[] motionX, motionY; - float[] motionPressure; - int[] mouseX, mouseY; - - AndroidTouchEvent(Object nativeObject, long millis, int action, int modifiers) { - super(nativeObject, millis, action, modifiers); - } - -// void setAction(int action) { -// this.action = action; -// } + protected void handleTouchEvent(TouchEvent event) { + touches = event.getTouches(touches); - void setNumPointers(int n) { - numPointers = n; - motionX = new float[n]; - motionY = new float[n]; - motionPressure = new float[n]; - mouseX = new int[n]; - mouseY = new int[n]; - } - - void setPointers(MotionEvent event) { - for (int ptIdx = 0; ptIdx < numPointers; ptIdx++) { - motionX[ptIdx] = event.getX(ptIdx); - motionY[ptIdx] = event.getY(ptIdx); - motionPressure[ptIdx] = event.getPressure(ptIdx); // should this be constrained? - mouseX[ptIdx] = (int) motionX[ptIdx]; //event.getRawX(); - mouseY[ptIdx] = (int) motionY[ptIdx]; //event.getRawY(); - } + switch (event.getAction()) { + case TouchEvent.START: + touchIsStarted = true; + break; + case TouchEvent.END: + touchIsStarted = false; + break; } - // Sets the pointers for the historical event histIdx - void setPointers(MotionEvent event, int hisIdx) { - for (int ptIdx = 0; ptIdx < numPointers; ptIdx++) { - motionX[ptIdx] = event.getHistoricalX(ptIdx, hisIdx); - motionY[ptIdx] = event.getHistoricalY(ptIdx, hisIdx); - motionPressure[ptIdx] = event.getHistoricalPressure(ptIdx, hisIdx); // should this be constrained? - mouseX[ptIdx] = (int) motionX[ptIdx]; //event.getRawX(); - mouseY[ptIdx] = (int) motionY[ptIdx]; //event.getRawY(); - } + handleMethods("touchEvent", new Object[] { event }); + + switch (event.getAction()) { + case TouchEvent.START: + touchStarted(event); + break; + case TouchEvent.END: + touchEnded(event); + break; + case TouchEvent.MOVE: + touchMoved(event); + break; + case TouchEvent.CANCEL: + touchCancelled(event); + break; } } -// Object motionLock = new Object(); -// AndroidTouchEvent[] motionEventQueue; -// int motionEventCount; -// -// protected void enqueueMotionEvent(MotionEvent event) { -// synchronized (motionLock) { -// // on first run, allocate array for motion events -// if (motionEventQueue == null) { -// motionEventQueue = new AndroidTouchEvent[20]; -// for (int i = 0; i < motionEventQueue.length; i++) { -// motionEventQueue[i] = new AndroidTouchEvent(); -// } -// } -// // allocate more PMotionEvent objects if we're out -// int historyCount = event.getHistorySize(); -//// println("motion: " + motionEventCount + " " + historyCount + " " + motionEventQueue.length); -// if (motionEventCount + historyCount >= motionEventQueue.length) { -// int atLeast = motionEventCount + historyCount + 1; -// AndroidTouchEvent[] temp = new AndroidTouchEvent[max(atLeast, motionEventCount << 1)]; -// if (PApplet.DEBUG) { -// println("motion: " + motionEventCount + " " + historyCount + " " + motionEventQueue.length); -// println("allocating " + temp.length + " entries for motion events"); -// } -// System.arraycopy(motionEventQueue, 0, temp, 0, motionEventCount); -// motionEventQueue = temp; -// for (int i = motionEventCount; i < motionEventQueue.length; i++) { -// motionEventQueue[i] = new AndroidTouchEvent(); -// } -// } -// -// // this will be the last event in the list -// AndroidTouchEvent pme = motionEventQueue[motionEventCount + historyCount]; -// pme.setAction(event.getAction()); -// pme.setNumPointers(event.getPointerCount()); -// pme.setPointers(event); -// -// // historical events happen before the 'current' values -// if (pme.action == MotionEvent.ACTION_MOVE && historyCount > 0) { -// for (int i = 0; i < historyCount; i++) { -// AndroidTouchEvent hist = motionEventQueue[motionEventCount++]; -// hist.setAction(event.getAction()); -// hist.setNumPointers(event.getPointerCount()); -// hist.setPointers(event, i); -// } -// } -// -// // now step over the last one that we used to assign 'pme' -// // if historyCount is 0, this just steps over the last -// motionEventCount++; -// } -// } -// -// -// protected void dequeueMotionEvents() { -// synchronized (motionLock) { -// for (int i = 0; i < motionEventCount; i++) { -// handleMotionEvent(motionEventQueue[i]); -// } -// motionEventCount = 0; -// } -// } - - - // ******************** NOT CURRENTLY IN USE ******************** - // Take action based on a motion event. - // Internally updates mouseX, mouseY, mousePressed, and mouseEvent. - // Then it calls the event type with no params, - // i.e. mousePressed() or mouseReleased() that the user may have - // overloaded to do something more useful. - protected void handleMotionEvent(AndroidTouchEvent pme) { - pmotionX = emotionX; - pmotionY = emotionY; - motionX = pme.motionX[0]; - motionY = pme.motionY[0]; - motionPressure = pme.motionPressure[0]; - - // replace previous mouseX/Y with the last from the event handlers - pmouseX = emouseX; - pmouseY = emouseY; - mouseX = pme.mouseX[0]; - mouseY = pme.mouseY[0]; - - // *** because removed from PApplet - boolean firstMotion = false; - // this used to only be called on mouseMoved and mouseDragged - // change it back if people run into trouble - if (firstMotion) { - pmouseX = mouseX; - pmouseY = mouseY; - dmouseX = mouseX; // set it as the first value to be used inside draw() too - dmouseY = mouseY; - - pmotionX = motionX; - pmotionY = motionY; - dmotionX = motionX; - dmotionY = motionY; - firstMotion = false; + /** + * Figure out how to process a mouse event. When loop() has been + * called, the events will be queued up until drawing is complete. + * If noLoop() has been called, then events will happen immediately. + */ + protected void nativeMotionEvent(MotionEvent motionEvent) { + int metaState = motionEvent.getMetaState(); + int modifiers = 0; + if ((metaState & android.view.KeyEvent.META_SHIFT_ON) != 0) { + modifiers |= Event.SHIFT; } - - // TODO implement method handling for registry of motion/mouse events -// MouseEvent me = new MouseEvent(nativeObject, millis, action, modifiers, x, y, button, clickCount); -// handleMethods("mouseEvent", new Object[] { mouseEvent }); -// handleMethods("motionEvent", new Object[] { motionEvent }); - - if (ppointersX.length < numPointers) { - ppointersX = new float[numPointers]; - ppointersY = new float[numPointers]; - ppointersPressure = new float[numPointers]; + if ((metaState & android.view.KeyEvent.META_CTRL_ON) != 0) { + modifiers |= Event.CTRL; + } + if ((metaState & android.view.KeyEvent.META_META_ON) != 0) { + modifiers |= Event.META; + } + if ((metaState & android.view.KeyEvent.META_ALT_ON) != 0) { + modifiers |= Event.ALT; } - arrayCopy(pointersX, ppointersX); - arrayCopy(pointersY, ppointersY); - arrayCopy(pointersPressure, ppointersPressure); - numPointers = pme.numPointers; - if (pointersX.length < numPointers) { - pointersX = new float[numPointers]; - pointersY = new float[numPointers]; - pointersPressure = new float[numPointers]; + int button; + int state = motionEvent.getButtonState(); + switch (state) { + case MotionEvent.BUTTON_PRIMARY: + button = LEFT; + break; + case MotionEvent.BUTTON_SECONDARY: + button = RIGHT; + break; + case MotionEvent.BUTTON_TERTIARY: + button = CENTER; + break; + default: + // Covers the BUTTON_FORWARD, BUTTON_BACK, + // BUTTON_STYLUS_PRIMARY, and BUTTON_STYLUS_SECONDARY + button = state; } - arrayCopy(pme.motionX, pointersX); - arrayCopy(pme.motionY, pointersY); - arrayCopy(pme.motionPressure, pointersPressure); - // Triggering the appropriate event methods - if (pme.action == MotionEvent.ACTION_DOWN || (!mousePressed && numPointers == 1)) { - // First pointer is down - mousePressed = true; - onePointerGesture = true; - twoPointerGesture = false; - downMillis = millis(); - downX = pointersX[0]; - downY = pointersY[0]; - - mousePressed(); - pressEvent(); - - } else if ((pme.action == MotionEvent.ACTION_POINTER_DOWN && numPointers == 2) || - (pme.action == MotionEvent.ACTION_POINTER_2_DOWN) || // 2.3 seems to use this action constant (supposedly deprecated) instead of ACTION_POINTER_DOWN - (pnumPointers == 1 && numPointers == 2)) { // 2.1 just uses MOVE as the action constant, so the only way to know we have a new pointer is to compare the counters. - - // An additional pointer is down (we keep track of multitouch only for 2 pointers) - onePointerGesture = false; - twoPointerGesture = true; - - } else if ((pme.action == MotionEvent.ACTION_POINTER_UP && numPointers == 2) || - (pme.action == MotionEvent.ACTION_POINTER_2_UP) || // 2.1 doesn't use the ACTION_POINTER_UP constant, but this one, apparently deprecated in newer versions of the SDK. - (twoPointerGesture && numPointers < 2)) { // Sometimes it seems that it doesn't generate the up event. - // A previously detected pointer is up - - twoPointerGesture = false; // Not doing a 2-pointer gesture anymore, but neither a 1-pointer. - - } else if (pme.action == MotionEvent.ACTION_MOVE) { - // Pointer motion - - if (onePointerGesture) { - if (mousePressed) { - mouseDragged(); - dragEvent(); - } else { // TODO is this physically possible? (perhaps with alt input devices...) - mouseMoved(); - moveEvent(); - } - } else if (twoPointerGesture) { - float d0 = PApplet.dist(ppointersX[0], ppointersY[0], ppointersX[1], ppointersY[1]); - float d1 = PApplet.dist(pointersX[0], pointersY[0], pointersX[1], pointersY[1]); + enqueueMouseEvents(motionEvent, button, modifiers); + enqueueTouchEvents(motionEvent, button, modifiers); + } - if (0 < d0 && 0 < d1) { - float centerX = 0.5f * (pointersX[0] + pointersX[1]); - float centerY = 0.5f * (pointersY[0] + pointersY[1]); - zoomEvent(centerX, centerY, d0, d1); - } - } + protected void enqueueTouchEvents(MotionEvent event, int button, int modifiers) { + int actionMasked = event.getActionMasked(); + int pAction = 0; + int pointerUp = 0; + int pointerUpIdx = -1; + switch (actionMasked) { + case MotionEvent.ACTION_DOWN: + pAction = TouchEvent.START; + break; + case MotionEvent.ACTION_POINTER_DOWN: + pAction = TouchEvent.START; + break; + case MotionEvent.ACTION_MOVE: + pAction = TouchEvent.MOVE; + break; + case MotionEvent.ACTION_UP: + pAction = TouchEvent.END; + break; + case MotionEvent.ACTION_POINTER_UP: + pAction = TouchEvent.END; + pointerUp = 1; + // We get the index of the pointer that is being released: + // https://developer.android.com/reference/android/view/MotionEvent#getActionIndex() + pointerUpIdx = event.getActionIndex(); + break; + default: + // Covers any other action value, including ACTION_CANCEL + pAction = TouchEvent.CANCEL; + break; + } - } else if (pme.action == MotionEvent.ACTION_UP) { - // Final pointer is up - mousePressed = false; + if (pAction == TouchEvent.START || pAction == TouchEvent.END || pAction == TouchEvent.CANCEL) { + touchPointerId = event.getPointerId(0); + } - float upX = pointersX[0]; - float upY = pointersY[0]; - float gestureLength = PApplet.dist(downX, downY, upX, upY); - - int upMillis = millis(); - int gestureTime = upMillis - downMillis; - - if (onePointerGesture) { - // First, lets determine if this 1-pointer event is a tap - boolean tap = gestureLength <= MAX_TAP_DISP && gestureTime <= MAX_TAP_DURATION; - if (tap) { - mouseClicked(); - tapEvent(downX, downY); - } else if (MIN_SWIPE_LENGTH <= gestureLength && gestureTime <= MAX_SWIPE_DURATION) { - mouseReleased(); - swipeEvent(downX, downY, upX, upY); - } else { - mouseReleased(); - releaseEvent(); - } + // getPointerCount() will return the count including the pointer that is being released, so + // we substract 1 if if this current event is a pointer up + int activePointerCount = event.getPointerCount() - pointerUp; - } else { - mouseReleased(); - releaseEvent(); + if (actionMasked == MotionEvent.ACTION_MOVE) { + // Post historical movement events, if any. + int historySize = event.getHistorySize(); + for (int h = 0; h < historySize; h++) { + TouchEvent touchEvent = new TouchEvent(event, event.getHistoricalEventTime(h), + pAction, modifiers, button); + touchEvent.setNumPointers(activePointerCount); + int p = 0; + for (int idx = 0; idx < event.getPointerCount(); idx++) { + if (idx == pointerUpIdx) continue; // Skip the released pointer + touchEvent.setPointer(p++, event.getPointerId(idx), event.getHistoricalX(idx, h), event.getHistoricalY(idx, h), + event.getHistoricalSize(idx, h), event.getHistoricalPressure(idx, h)); + } + postEvent(touchEvent); } - onePointerGesture = twoPointerGesture = false; - - } else if (pme.action == MotionEvent.ACTION_CANCEL) { - // Current gesture is canceled. - onePointerGesture = twoPointerGesture = false; - mousePressed = false; - mouseReleased(); - releaseEvent(); - - } else { - //System.out.println("Unknown MotionEvent action: " + action); } - pnumPointers = numPointers; - - if (pme.action == MotionEvent.ACTION_MOVE) { - emotionX = motionX; - emotionY = motionY; - emouseX = mouseX; - emouseY = mouseY; + // Current event + TouchEvent touchEvent = new TouchEvent(event, event.getEventTime(), + pAction, modifiers, button); + if (actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_CANCEL) { + // Last pointer up + touchEvent.setNumPointers(0); + } else { + // We still have some pointers left + touchEvent.setNumPointers(activePointerCount); + int p = 0; + for (int idx = 0; idx < event.getPointerCount(); idx++) { + if (idx == pointerUpIdx) continue; // Skip the released pointer + touchEvent.setPointer(p++, event.getPointerId(idx), event.getX(idx), event.getY(idx), + event.getSize(idx), event.getPressure(idx)); + } } + postEvent(touchEvent); } - */ - - - // Added in API 11, so defined here: Constant Value: 4096 (0x00001000) - static final int META_CTRL_ON = 4096; - // Added in API 11, so defined here: 65536 (0x00010000) - static final int META_META_ON = 65536; - - int motionPointerId; - - - /** - * Figure out how to process a mouse event. When loop() has been - * called, the events will be queued up until drawing is complete. - * If noLoop() has been called, then events will happen immediately. - */ - protected void nativeMotionEvent(android.view.MotionEvent motionEvent) { -// enqueueMotionEvent(event); -// -// // this will be the last event in the list -// AndroidTouchEvent pme = motionEventQueue[motionEventCount + historyCount]; -// pme.setAction(event.getAction()); -// pme.setNumPointers(event.getPointerCount()); -// pme.setPointers(event); -// -// // historical events happen before the 'current' values -// if (pme.action == MotionEvent.ACTION_MOVE && historyCount > 0) { -// for (int i = 0; i < historyCount; i++) { -// AndroidTouchEvent hist = motionEventQueue[motionEventCount++]; -// hist.setAction(event.getAction()); -// hist.setNumPointers(event.getPointerCount()); -// hist.setPointers(event, i); -// } -// } - - // ACTION_HOVER_ENTER and ACTION_HOVER_EXIT are passed into - // onGenericMotionEvent(android.view.MotionEvent) - // if we want to implement mouseEntered/Exited - // http://developer.android.com/reference/android/view/MotionEvent.html - // http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html - // http://www.techrepublic.com/blog/app-builder/use-androids-gesture-detector-to-translate-a-swipe-into-an-event/1577 - int metaState = motionEvent.getMetaState(); - int modifiers = 0; - if ((metaState & android.view.KeyEvent.META_SHIFT_ON) != 0) { - modifiers |= Event.SHIFT; - } - if ((metaState & META_CTRL_ON) != 0) { - modifiers |= Event.CTRL; - } - if ((metaState & META_META_ON) != 0) { - modifiers |= Event.META; - } - if ((metaState & android.view.KeyEvent.META_ALT_ON) != 0) { - modifiers |= Event.ALT; - } + protected void enqueueMouseEvents(MotionEvent event, int button, int modifiers) { + int actionMasked = event.getActionMasked(); int clickCount = 1; // not really set... (i.e. not catching double taps) int index; - // MotionEvent.html -> getButtonState() does BUTTON_PRIMARY, SECONDARY, TERTIARY - // use this for left/right/etc - switch (motionEvent.getAction()) { + switch (actionMasked) { case MotionEvent.ACTION_DOWN: - motionPointerId = motionEvent.getPointerId(0); - postEvent(new MouseEvent(motionEvent, motionEvent.getEventTime(), + mousePointerId = event.getPointerId(0); + postEvent(new MouseEvent(event, event.getEventTime(), MouseEvent.PRESS, modifiers, - (int) motionEvent.getX(), (int) motionEvent.getY(), - LEFT, clickCount)); + (int) event.getX(), (int) event.getY(), + button, clickCount)); break; case MotionEvent.ACTION_MOVE: -// int historySize = motionEvent.getHistorySize(); - index = motionEvent.findPointerIndex(motionPointerId); + index = event.findPointerIndex(mousePointerId); if (index != -1) { - postEvent(new MouseEvent(motionEvent, motionEvent.getEventTime(), + postEvent(new MouseEvent(event, event.getEventTime(), MouseEvent.DRAG, modifiers, - (int) motionEvent.getX(index), (int) motionEvent.getY(index), - LEFT, clickCount)); + (int) event.getX(index), (int) event.getY(index), + button, clickCount)); } break; case MotionEvent.ACTION_UP: - index = motionEvent.findPointerIndex(motionPointerId); + index = event.findPointerIndex(mousePointerId); if (index != -1) { - postEvent(new MouseEvent(motionEvent, motionEvent.getEventTime(), + postEvent(new MouseEvent(event, event.getEventTime(), MouseEvent.RELEASE, modifiers, - (int) motionEvent.getX(index), (int) motionEvent.getY(index), - LEFT, clickCount)); + (int) event.getX(index), (int) event.getY(index), + button, clickCount)); } break; } - //postEvent(pme); } - public void mousePressed() { } @@ -2733,25 +2453,36 @@ public void mouseExited(MouseEvent event) { } + public void touchStarted() { } - ////////////////////////////////////////////////////////////// - // unfinished API, do not use + public void touchStarted(TouchEvent event) { + touchStarted(); + } -// protected void pressEvent() { } -// -// protected void dragEvent() { } -// -// protected void moveEvent() { } -// -// protected void releaseEvent() { } -// -// protected void zoomEvent(float x, float y, float d0, float d1) { } -// -// protected void tapEvent(float x, float y) { } -// -// protected void swipeEvent(float x0, float y0, float x1, float y1) { } + public void touchMoved() { } + + + public void touchMoved(TouchEvent event) { + touchMoved(); + } + + + public void touchEnded() { } + + + public void touchEnded(TouchEvent event) { + touchEnded(); + } + + + public void touchCancelled() { } + + + public void touchCancelled(TouchEvent event) { + touchCancelled(); + } ////////////////////////////////////////////////////////////// @@ -2780,6 +2511,10 @@ public void mouseExited(MouseEvent event) { protected void handleKeyEvent(KeyEvent event) { + + // Get rid of auto-repeating keys if desired and supported + if (!keyRepeatEnabled && event.isAutoRepeat()) return; + // keyEvent = event; key = event.getKey(); keyCode = event.getKeyCode(); @@ -2799,12 +2534,6 @@ protected void handleKeyEvent(KeyEvent event) { } - @Override - public void onBackPressed() { - exit(); - } - - protected void nativeKeyEvent(android.view.KeyEvent event) { // event.isPrintingKey() returns false for whitespace and others, // which is a problem if the space bar or tab key are used. @@ -2828,12 +2557,33 @@ protected void nativeKeyEvent(android.view.KeyEvent event) { int keModifiers = 0; KeyEvent ke = new KeyEvent(event, event.getEventTime(), - keAction, keModifiers, key, keyCode); + keAction, keModifiers, key, keyCode, 0 < event.getRepeatCount()); postEvent(ke); } + public void openKeyboard() { + Context context = surface.getContext(); + InputMethodManager imm = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0); + keyboardIsOpen = true; + } + + + public void closeKeyboard() { + if (keyboardIsOpen) { + Context context = surface.getContext(); + InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + keyboardIsOpen = false; + if (parentLayout == -1) { + setFullScreenVisibility(); + } + } + } + + public void keyPressed() { } @@ -2853,14 +2603,10 @@ public void keyReleased(KeyEvent event) { } - /** - * Never currently called (does not exist) on Android. - * http://code.google.com/p/processing/issues/detail?id=1489 - */ public void keyTyped() { } - public void keyTyped(KeyEvent event ) { + public void keyTyped(KeyEvent event) { keyTyped(); } @@ -2889,8 +2635,6 @@ public void focusLost() { } // getting the time - static protected Time time = new Time(); - /** * Get the number of milliseconds since the applet started. *

    @@ -2903,16 +2647,12 @@ public int millis() { /** Seconds position of the current time. */ static public int second() { - //return Calendar.getInstance().get(Calendar.SECOND); - time.setToNow(); - return time.second; + return Calendar.getInstance().get(Calendar.SECOND); } /** Minutes position of the current time. */ static public int minute() { - //return Calendar.getInstance().get(Calendar.MINUTE); - time.setToNow(); - return time.minute; + return Calendar.getInstance().get(Calendar.MINUTE); } /** @@ -2923,9 +2663,7 @@ static public int minute() { * if (yankeeHour == 0) yankeeHour = 12; */ static public int hour() { - //return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); - time.setToNow(); - return time.hour; + return Calendar.getInstance().get(Calendar.HOUR_OF_DAY); } /** @@ -2935,9 +2673,7 @@ static public int hour() { * or day of the year (1..365) then use java's Calendar.get() */ static public int day() { - //return Calendar.getInstance().get(Calendar.DAY_OF_MONTH); - time.setToNow(); - return time.monthDay; + return Calendar.getInstance().get(Calendar.DAY_OF_MONTH); } /** @@ -2945,18 +2681,14 @@ static public int day() { */ static public int month() { // months are number 0..11 so change to colloquial 1..12 - //return Calendar.getInstance().get(Calendar.MONTH) + 1; - time.setToNow(); - return time.month + 1; + return Calendar.getInstance().get(Calendar.MONTH) + 1; } /** * Get the current year. */ static public int year() { - //return Calendar.getInstance().get(Calendar.YEAR); - time.setToNow(); - return time.year; + return Calendar.getInstance().get(Calendar.YEAR); } @@ -3002,10 +2734,12 @@ public void delay(int napTime) { * * ( end auto-generated ) */ - public void frameRate(float newRateTarget) { - frameRateTarget = newRateTarget; - frameRatePeriod = (long) (1000000000.0 / frameRateTarget); - g.setFrameRate(newRateTarget); + public void frameRate(float fps) { +// +// frameRateTarget = newRateTarget; +// frameRatePeriod = (long) (1000000000.0 / frameRateTarget); +// g.setFrameRate(newRateTarget); + surface.setFrameRate(fps); } @@ -3060,7 +2794,7 @@ public void link(String here) { */ public void link(String url, String frameTitle) { Intent viewIntent = new Intent("android.intent.action.VIEW", Uri.parse(url)); - startActivity(viewIntent); + surface.startActivity(viewIntent); } @@ -3087,7 +2821,6 @@ static public Process exec(String[] argv) { try { return Runtime.getRuntime().exec(argv); } catch (Exception e) { - e.printStackTrace(); throw new RuntimeException("Could not open " + join(argv, ' ')); } } @@ -3096,6 +2829,15 @@ static public Process exec(String[] argv) { ////////////////////////////////////////////////////////////// + /** + * Better way of handling e.printStackTrace() calls so that they can be + * handled by subclasses as necessary. + */ + protected void printStackTrace(Throwable t) { + t.printStackTrace(); + } + + /** * Function for an applet/application to kill itself and * display an error. Mostly this is here to be improved later. @@ -3116,42 +2858,22 @@ public void die(String what, Exception e) { /** - * Call to safely exit the sketch when finished. For instance, - * to render a single frame, save it, and quit. + * Conveniency method so perform initialization tasks when the activity is + * created, while avoiding the ackward call to onCreate() with the bundle + * and super.onCreate(). */ - public void exit() { -// println("exit() called"); - if (thread == null) { - // exit immediately, stop() has already been called, - // meaning that the main thread has long since exited - exit2(); - - } else if (looping) { - // stop() will be called as the thread exits - finished = true; - // tell the code to call exit2() to do a System.exit() - // once the next draw() has completed - exitCalled = true; - - } else if (!looping) { - // if not looping, shut down things explicitly, - // because the main thread will be sleeping - dispose(); + public void create() { - // now get out - exit2(); - } } - - void exit2() { - try { - System.exit(0); - } catch (SecurityException e) { - // don't care about applet security exceptions - } + /** + * Should trigger a graceful activity/service shutdown (calling onPause/onStop, etc). + */ + public void exit() { + surface.finish(); } + /** * Called to dispose of resources and shut down the sketch. * Destroys the thread, dispose the renderer, and notify listeners. @@ -3163,12 +2885,15 @@ final public void dispose() { // moved here from stop() finished = true; // let the sketch know it is shut down time - // don't run stop and disposers twice - if (thread == null) return; - thread = null; - // call to shut down renderer, in case it needs it (pdf does) - if (g != null) g.dispose(); + if (surface != null) { + surface.stopThread(); + surface.dispose(); + } + if (g != null) { + g.clearState(); // This should probably go in dispose, but for the time being... + g.dispose(); + } handleMethods("dispose"); } @@ -3191,16 +2916,16 @@ public void method(String name) { method.invoke(this, new Object[] { }); } catch (IllegalArgumentException e) { - e.printStackTrace(); + printStackTrace(e); } catch (IllegalAccessException e) { - e.printStackTrace(); + printStackTrace(e); } catch (InvocationTargetException e) { e.getTargetException().printStackTrace(); } catch (NoSuchMethodException nsme) { System.err.println("There is no public " + name + "() method " + "in the class " + getClass().getName()); } catch (Exception e) { - e.printStackTrace(); + printStackTrace(e); } } @@ -3422,6 +3147,25 @@ static public void print(String what) { System.out.flush(); } + /** + * @param variables list of data, separated by commas + */ + static public void print(Object... variables) { + StringBuilder sb = new StringBuilder(); + for (Object o : variables) { + if (sb.length() != 0) { + sb.append(" "); + } + if (o == null) { + sb.append("null"); + } else { + sb.append(o.toString()); + } + } + System.out.print(sb.toString()); + } + + /* static public void print(Object what) { if (what == null) { // special case since this does fuggly things on > 1.1 @@ -3430,6 +3174,7 @@ static public void print(Object what) { System.out.println(what.toString()); } } + */ // @@ -3463,6 +3208,15 @@ static public void println(String what) { print(what); System.out.println(); } + /** + * @param variables list of data, separated by commas + */ + static public void println(Object... variables) { +// System.out.println("got " + variables.length + " variables"); + print(variables); + println(); + } + static public void println(Object what) { if (what == null) { // special case since this does fuggly things on > 1.1 @@ -3543,6 +3297,100 @@ static public void println(Object what) { } } + +/** + * @webref output:text_area + * @param what one-dimensional array + * @usage IDE + * @see PApplet#print(byte) + * @see PApplet#println() + */ + static public void printArray(Object what) { + if (what == null) { + // special case since this does fuggly things on > 1.1 + System.out.println("null"); + + } else { + String name = what.getClass().getName(); + if (name.charAt(0) == '[') { + switch (name.charAt(1)) { + case '[': + // don't even mess with multi-dimensional arrays (case '[') + // or anything else that's not int, float, boolean, char + System.out.println(what); + break; + + case 'L': + // print a 1D array of objects as individual elements + Object poo[] = (Object[]) what; + for (int i = 0; i < poo.length; i++) { + if (poo[i] instanceof String) { + System.out.println("[" + i + "] \"" + poo[i] + "\""); + } else { + System.out.println("[" + i + "] " + poo[i]); + } + } + break; + + case 'Z': // boolean + boolean zz[] = (boolean[]) what; + for (int i = 0; i < zz.length; i++) { + System.out.println("[" + i + "] " + zz[i]); + } + break; + + case 'B': // byte + byte bb[] = (byte[]) what; + for (int i = 0; i < bb.length; i++) { + System.out.println("[" + i + "] " + bb[i]); + } + break; + + case 'C': // char + char cc[] = (char[]) what; + for (int i = 0; i < cc.length; i++) { + System.out.println("[" + i + "] '" + cc[i] + "'"); + } + break; + + case 'I': // int + int ii[] = (int[]) what; + for (int i = 0; i < ii.length; i++) { + System.out.println("[" + i + "] " + ii[i]); + } + break; + + case 'J': // int + long jj[] = (long[]) what; + for (int i = 0; i < jj.length; i++) { + System.out.println("[" + i + "] " + jj[i]); + } + break; + + case 'F': // float + float ff[] = (float[]) what; + for (int i = 0; i < ff.length; i++) { + System.out.println("[" + i + "] " + ff[i]); + } + break; + + case 'D': // double + double dd[] = (double[]) what; + for (int i = 0; i < dd.length; i++) { + System.out.println("[" + i + "] " + dd[i]); + } + break; + + default: + System.out.println(what); + } + } else { // not an array + System.out.println(what); + } + } + System.out.flush(); + } + // /* @@ -3815,54 +3663,110 @@ static public final float map(float value, Random internalRandom; /** - * Return a random number in the range [0, howbig). - *

    - * The number returned will range from zero up to - * (but not including) 'howbig'. + * */ - public final float random(float howbig) { + public final float random(float high) { + // avoid an infinite loop when 0 or NaN are passed in + if (high == 0 || high != high) { + return 0; + } + + if (internalRandom == null) { + internalRandom = new Random(); + } + // for some reason (rounding error?) Math.random() * 3 // can sometimes return '3' (once in ~30 million tries) // so a check was added to avoid the inclusion of 'howbig' + float value = 0; + do { + value = internalRandom.nextFloat() * high; + } while (value == high); + return value; + } - // avoid an infinite loop - if (howbig == 0) return 0; + /** + * ( begin auto-generated from randomGaussian.xml ) + * + * Returns a float from a random series of numbers having a mean of 0 + * and standard deviation of 1. Each time the randomGaussian() + * function is called, it returns a number fitting a Gaussian, or + * normal, distribution. There is theoretically no minimum or maximum + * value that randomGaussian() might return. Rather, there is + * just a very low probability that values far from the mean will be + * returned; and a higher probability that numbers near the mean will + * be returned. + * + * ( end auto-generated ) + * @webref math:random + * @see PApplet#random(float,float) + * @see PApplet#noise(float, float, float) + */ + public final float randomGaussian() { + if (internalRandom == null) { + internalRandom = new Random(); + } + return (float) internalRandom.nextGaussian(); + } - // internal random number object - if (internalRandom == null) internalRandom = new Random(); + /** + * ( begin auto-generated from random.xml ) + * + * Generates random numbers. Each time the random() function is + * called, it returns an unexpected value within the specified range. If + * one parameter is passed to the function it will return a float + * between zero and the value of the high parameter. The function + * call random(5) returns values between 0 and 5 (starting at zero, + * up to but not including 5). If two parameters are passed, it will return + * a float with a value between the the parameters. The function + * call random(-5, 10.2) returns values starting at -5 up to (but + * not including) 10.2. To convert a floating-point random number to an + * integer, use the int() function. + * + * ( end auto-generated ) + * @webref math:random + * @param low lower limit + * @param high upper limit + * @see PApplet#randomSeed(long) + * @see PApplet#noise(float, float, float) + */ + public final float random(float low, float high) { + if (low >= high) return low; + float diff = high - low; float value = 0; + // because of rounding error, can't just add low, otherwise it may hit high + // https://github.com/processing/processing/issues/4551 do { - //value = (float)Math.random() * howbig; - value = internalRandom.nextFloat() * howbig; - } while (value == howbig); + value = random(diff) + low; + } while (value == high); return value; } /** - * Return a random number in the range [howsmall, howbig). - *

    - * The number returned will range from 'howsmall' up to - * (but not including 'howbig'. - *

    - * If howsmall is >= howbig, howsmall will be returned, - * meaning that random(5, 5) will return 5 (useful) - * and random(7, 4) will return 7 (not useful.. better idea?) + * ( begin auto-generated from randomSeed.xml ) + * + * Sets the seed value for random(). By default, random() + * produces different results each time the program is run. Set the + * value parameter to a constant to return the same pseudo-random + * numbers each time the software is run. + * + * ( end auto-generated ) + * @webref math:random + * @param seed seed value + * @see PApplet#random(float,float) + * @see PApplet#noise(float, float, float) + * @see PApplet#noiseSeed(long) */ - public final float random(float howsmall, float howbig) { - if (howsmall >= howbig) return howsmall; - float diff = howbig - howsmall; - return random(diff) + howsmall; + public final void randomSeed(long seed) { + if (internalRandom == null) { + internalRandom = new Random(); + } + internalRandom.setSeed(seed); } - public final void randomSeed(long what) { - // internal random number object - if (internalRandom == null) internalRandom = new Random(); - internalRandom.setSeed(what); - } - ////////////////////////////////////////////////////////////// @@ -3899,7 +3803,6 @@ public final void randomSeed(long what) { /** - * Computes the Perlin noise function value at point x. */ public float noise(float x) { // is this legit? it's a dumb way to do it (but repair it later) @@ -3907,15 +3810,50 @@ public float noise(float x) { } /** - * Computes the Perlin noise function value at the point x, y. */ public float noise(float x, float y) { return noise(x, y, 0f); } /** - * Computes the Perlin noise function value at x, y, z. - */ + * ( begin auto-generated from noise.xml ) + * + * Returns the Perlin noise value at specified coordinates. Perlin noise is + * a random sequence generator producing a more natural ordered, harmonic + * succession of numbers compared to the standard random() function. + * It was invented by Ken Perlin in the 1980s and been used since in + * graphical applications to produce procedural textures, natural motion, + * shapes, terrains etc.

    The main difference to the + * random() function is that Perlin noise is defined in an infinite + * n-dimensional space where each pair of coordinates corresponds to a + * fixed semi-random value (fixed only for the lifespan of the program). + * The resulting value will always be between 0.0 and 1.0. Processing can + * compute 1D, 2D and 3D noise, depending on the number of coordinates + * given. The noise value can be animated by moving through the noise space + * as demonstrated in the example above. The 2nd and 3rd dimension can also + * be interpreted as time.

    The actual noise is structured + * similar to an audio signal, in respect to the function's use of + * frequencies. Similar to the concept of harmonics in physics, perlin + * noise is computed over several octaves which are added together for the + * final result.

    Another way to adjust the character of the + * resulting sequence is the scale of the input coordinates. As the + * function works within an infinite space the value of the coordinates + * doesn't matter as such, only the distance between successive coordinates + * does (eg. when using noise() within a loop). As a general rule + * the smaller the difference between coordinates, the smoother the + * resulting noise sequence will be. Steps of 0.005-0.03 work best for most + * applications, but this will differ depending on use. + * + * ( end auto-generated ) + * + * @webref math:random + * @param x x-coordinate in noise space + * @param y y-coordinate in noise space + * @param z z-coordinate in noise space + * @see PApplet#noiseSeed(long) + * @see PApplet#noiseDetail(int, float) + * @see PApplet#random(float,float) + */ public float noise(float x, float y, float z) { if (perlin == null) { if (perlinRandom == null) { @@ -3938,9 +3876,9 @@ public float noise(float x, float y, float z) { if (z<0) z=-z; int xi=(int)x, yi=(int)y, zi=(int)z; - float xf = (float)(x-xi); - float yf = (float)(y-yi); - float zf = (float)(z-zi); + float xf = x - xi; + float yf = y - yi; + float zf = z - zi; float rxf, ryf; float r=0; @@ -3995,18 +3933,61 @@ private float noise_fsc(float i) { // for different levels of detail. lower values will produce // smoother results as higher octaves are surpressed + /** + * ( begin auto-generated from noiseDetail.xml ) + * + * Adjusts the character and level of detail produced by the Perlin noise + * function. Similar to harmonics in physics, noise is computed over + * several octaves. Lower octaves contribute more to the output signal and + * as such define the overal intensity of the noise, whereas higher octaves + * create finer grained details in the noise sequence. By default, noise is + * computed over 4 octaves with each octave contributing exactly half than + * its predecessor, starting at 50% strength for the 1st octave. This + * falloff amount can be changed by adding an additional function + * parameter. Eg. a falloff factor of 0.75 means each octave will now have + * 75% impact (25% less) of the previous lower octave. Any value between + * 0.0 and 1.0 is valid, however note that values greater than 0.5 might + * result in greater than 1.0 values returned by noise().

    By changing these parameters, the signal created by the noise() + * function can be adapted to fit very specific needs and characteristics. + * + * ( end auto-generated ) + * @webref math:random + * @param lod number of octaves to be used by the noise + * @see PApplet#noise(float, float, float) + */ public void noiseDetail(int lod) { if (lod>0) perlin_octaves=lod; } + /** + * @see #noiseDetail(int) + * @param falloff falloff factor for each octave + */ public void noiseDetail(int lod, float falloff) { if (lod>0) perlin_octaves=lod; if (falloff>0) perlin_amp_falloff=falloff; } - public void noiseSeed(long what) { + /** + * ( begin auto-generated from noiseSeed.xml ) + * + * Sets the seed value for noise(). By default, noise() + * produces different results each time the program is run. Set the + * value parameter to a constant to return the same pseudo-random + * numbers each time the software is run. + * + * ( end auto-generated ) + * @webref math:random + * @param seed seed value + * @see PApplet#noise(float, float, float) + * @see PApplet#noiseDetail(int, float) + * @see PApplet#random(float,float) + * @see PApplet#randomSeed(long) + */ + public void noiseSeed(long seed) { if (perlinRandom == null) perlinRandom = new Random(); - perlinRandom.setSeed(what); + perlinRandom.setSeed(seed); // force table reset after changing the random number seed [0122] perlin = null; } @@ -4016,13 +3997,6 @@ public void noiseSeed(long what) { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -// protected String[] loadImageFormats; - - -// public PImage loadImage(String filename) { -// return loadImage(filename, null); -// } - public PImage loadImage(String filename) { //, Object params) { // return loadImage(filename, null); @@ -4043,95 +4017,20 @@ public PImage loadImage(String filename) { //, Object params) { } // int much = (int) (System.currentTimeMillis() - t); // println("loadImage(" + filename + ") was " + nfc(much)); - PImage image = new PImage(bitmap); - image.parent = this; -// if (params != null) { -// image.setParams(g, params); -// } - return image; + if (bitmap == null) { + System.err.println("Could not load the image because the bitmap was empty."); + return null; + } else { + PImage image = new PImage(bitmap); + image.parent = this; + return image; + } } - /* public PImage loadImage(String filename, String extension) { - if (extension == null) { - String lower = filename.toLowerCase(); - int dot = filename.lastIndexOf('.'); - if (dot == -1) { - extension = "unknown"; // no extension found - } - extension = lower.substring(dot + 1); - - // check for, and strip any parameters on the url, i.e. - // filename.jpg?blah=blah&something=that - int question = extension.indexOf('?'); - if (question != -1) { - extension = extension.substring(0, question); - } - } - - // just in case. them users will try anything! - extension = extension.toLowerCase(); - - if (extension.equals("tga")) { - try { - return loadImageTGA(filename); - } catch (IOException e) { - e.printStackTrace(); - return null; - } - } - - if (extension.equals("tif") || extension.equals("tiff")) { - byte bytes[] = loadBytes(filename); - return (bytes == null) ? null : PImage.loadTIFF(bytes); - } - - // For jpeg, gif, and png, load them using createImage(), - // because the javax.imageio code was found to be much slower, see - // Bug 392. - try { - if (extension.equals("jpg") || extension.equals("jpeg") || - extension.equals("gif") || extension.equals("png") || - extension.equals("unknown")) { - byte bytes[] = loadBytes(filename); - if (bytes == null) { - return null; - } else { - Image awtImage = Toolkit.getDefaultToolkit().createImage(bytes); - PImage image = loadImageMT(awtImage); - if (image.width == -1) { - System.err.println("The file " + filename + - " contains bad image data, or may not be an image."); - } - // if it's a .gif image, test to see if it has transparency - if (extension.equals("gif") || extension.equals("png")) { - image.checkAlpha(); - } - return image; - } - } - } catch (Exception e) { - // show error, but move on to the stuff below, see if it'll work - e.printStackTrace(); - } - - if (loadImageFormats == null) { - loadImageFormats = ImageIO.getReaderFormatNames(); - } - if (loadImageFormats != null) { - for (int i = 0; i < loadImageFormats.length; i++) { - if (extension.equals(loadImageFormats[i])) { - return loadImageIO(filename); - } - } - } - - // failed, could not load image after all those attempts - System.err.println("Could not find a method to load " + filename); - return null; + return loadImage(filename); } - */ public PImage requestImage(String filename) { @@ -4188,6 +4087,10 @@ public void run() { vessel.pixels = actual.pixels; // an android, pixels[] will probably be null, we want this one vessel.bitmap = actual.bitmap; + + vessel.pixelWidth = actual.width; + vessel.pixelHeight = actual.height; + vessel.pixelDensity = 1; } requestImageCount--; } @@ -4195,31 +4098,6 @@ public void run() { - ////////////////////////////////////////////////////////////// - - // EXTENSIONS - - - /** - * Get the compression-free extension for this filename. - * @param filename The filename to check - * @return an extension, skipping past .gz if it's present - */ - static public String checkExtension(String filename) { - // Don't consider the .gz as part of the name, createInput() - // and createOuput() will take care of fixing that up. - if (filename.toLowerCase().endsWith(".gz")) { - filename = filename.substring(0, filename.length() - 3); - } - int dotIndex = filename.lastIndexOf('.'); - if (dotIndex != -1) { - return filename.substring(dotIndex + 1).toLowerCase(); - } - return null; - } - - - ////////////////////////////////////////////////////////////// // DATA I/O @@ -4229,7 +4107,7 @@ public XML createXML(String name) { try { return new XML(name); } catch (Exception e) { - e.printStackTrace(); + printStackTrace(e); return null; } } @@ -4253,7 +4131,7 @@ public XML loadXML(String filename, String options) { try { return new XML(createInput(filename), options); } catch (Exception e) { - e.printStackTrace(); + printStackTrace(e); return null; } } @@ -4268,7 +4146,7 @@ public XML parseXML(String xmlString, String options) { try { return XML.parse(xmlString, options); } catch (Exception e) { - e.printStackTrace(); + printStackTrace(e); return null; } } @@ -4284,6 +4162,137 @@ public boolean saveXML(XML xml, String filename, String options) { } + /** + * @webref input:files + * @param input String to parse as a JSONObject + * @see PApplet#loadJSONObject(String) + * @see PApplet#saveJSONObject(JSONObject, String) + */ + public JSONObject parseJSONObject(String input) { + return new JSONObject(new StringReader(input)); + } + + + /** + * @webref input:files + * @param filename name of a file in the data folder or a URL + * @see JSONObject + * @see JSONArray + * @see PApplet#loadJSONArray(String) + * @see PApplet#saveJSONObject(JSONObject, String) + * @see PApplet#saveJSONArray(JSONArray, String) + */ + public JSONObject loadJSONObject(String filename) { + // can't pass of createReader() to the constructor b/c of resource leak + BufferedReader reader = createReader(filename); + JSONObject outgoing = new JSONObject(reader); + try { + reader.close(); + } catch (IOException e) { // not sure what would cause this + e.printStackTrace(); + } + return outgoing; + } + + + static public JSONObject loadJSONObject(File file) { + // can't pass of createReader() to the constructor b/c of resource leak + BufferedReader reader = createReader(file); + JSONObject outgoing = new JSONObject(reader); + try { + reader.close(); + } catch (IOException e) { // not sure what would cause this + e.printStackTrace(); + } + return outgoing; + } + + + /** + * @webref output:files + * @see JSONObject + * @see JSONArray + * @see PApplet#loadJSONObject(String) + * @see PApplet#loadJSONArray(String) + * @see PApplet#saveJSONArray(JSONArray, String) + */ + public boolean saveJSONObject(JSONObject json, String filename) { + return saveJSONObject(json, filename, null); + } + + + /** + * @nowebref + */ + public boolean saveJSONObject(JSONObject json, String filename, String options) { + return json.save(saveFile(filename), options); + } + + + /** + * @webref input:files + * @param input String to parse as a JSONArray + * @see JSONObject + * @see PApplet#loadJSONObject(String) + * @see PApplet#saveJSONObject(JSONObject, String) + */ + public JSONArray parseJSONArray(String input) { + return new JSONArray(new StringReader(input)); + } + + + /** + * @webref input:files + * @param filename name of a file in the data folder or a URL + * @see JSONArray + * @see PApplet#loadJSONObject(String) + * @see PApplet#saveJSONObject(JSONObject, String) + * @see PApplet#saveJSONArray(JSONArray, String) + */ + public JSONArray loadJSONArray(String filename) { + // can't pass of createReader() to the constructor b/c of resource leak + BufferedReader reader = createReader(filename); + JSONArray outgoing = new JSONArray(reader); + try { + reader.close(); + } catch (IOException e) { // not sure what would cause this + e.printStackTrace(); + } + return outgoing; + } + + + static public JSONArray loadJSONArray(File file) { + // can't pass of createReader() to the constructor b/c of resource leak + BufferedReader reader = createReader(file); + JSONArray outgoing = new JSONArray(reader); + try { + reader.close(); + } catch (IOException e) { // not sure what would cause this + e.printStackTrace(); + } + return outgoing; + } + + + /** + * @webref output:files + * @see JSONObject + * @see JSONArray + * @see PApplet#loadJSONObject(String) + * @see PApplet#loadJSONArray(String) + * @see PApplet#saveJSONObject(JSONObject, String) + */ + public boolean saveJSONArray(JSONArray json, String filename) { + return saveJSONArray(json, filename, null); + } + + + public boolean saveJSONArray(JSONArray json, String filename, String options) { + return json.save(saveFile(filename), options); + } + + public Table createTable() { return new Table(); } @@ -4316,7 +4325,7 @@ public Table loadTable(String filename, String options) { return new Table(createInput(filename), options); } catch (IOException e) { - e.printStackTrace(); + printStackTrace(e); return null; } } @@ -4332,7 +4341,7 @@ public boolean saveTable(Table table, String filename, String options) { table.save(saveFile(filename), options); return true; } catch (IOException e) { - e.printStackTrace(); + printStackTrace(e); } return false; } @@ -4388,7 +4397,7 @@ public PFont createFont(String name, float size, Typeface baseFont = null; if (lowerName.endsWith(".otf") || lowerName.endsWith(".ttf")) { - AssetManager assets = getBaseContext().getAssets(); + AssetManager assets = surface.getAssets(); baseFont = Typeface.createFromAsset(assets, name); } else { baseFont = (Typeface) PFont.findNative(name); @@ -4541,6 +4550,156 @@ public PFont createFont(String name, float size, // } + ////////////////////////////////////////////////////////////// + + // LISTING DIRECTORIES + + + public String[] listPaths(String path, String... options) { + File[] list = listFiles(path, options); + + int offset = 0; + for (String opt : options) { + if (opt.equals("relative")) { + if (!path.endsWith(File.pathSeparator)) { + path += File.pathSeparator; + } + offset = path.length(); + break; + } + } + String[] outgoing = new String[list.length]; + for (int i = 0; i < list.length; i++) { + // as of Java 1.8, substring(0) returns the original object + outgoing[i] = list[i].getAbsolutePath().substring(offset); + } + return outgoing; + } + + + public File[] listFiles(String path, String... options) { + File file = new File(path); + // if not an absolute path, make it relative to the sketch folder + if (!file.isAbsolute()) { + file = sketchFile(path); + } + return listFiles(file, options); + } + + + // "relative" -> no effect with the Files version, but important for listPaths + // "recursive" + // "extension=js" or "extensions=js|csv|txt" (no dot) + // "directories" -> only directories + // "files" -> only files + // "hidden" -> include hidden files (prefixed with .) disabled by default + static public File[] listFiles(File base, String... options) { + boolean recursive = false; + String[] extensions = null; + boolean directories = true; + boolean files = true; + boolean hidden = false; + + for (String opt : options) { + if (opt.equals("recursive")) { + recursive = true; + } else if (opt.startsWith("extension=")) { + extensions = new String[] { opt.substring(10) }; + } else if (opt.startsWith("extensions=")) { + extensions = split(opt.substring(10), ','); + } else if (opt.equals("files")) { + directories = false; + } else if (opt.equals("directories")) { + files = false; + } else if (opt.equals("hidden")) { + hidden = true; + } else if (opt.equals("relative")) { + // ignored + } else { + throw new RuntimeException(opt + " is not a listFiles() option"); + } + } + + if (extensions != null) { + for (int i = 0; i < extensions.length; i++) { + extensions[i] = "." + extensions[i]; + } + } + + if (!files && !directories) { + // just make "only files" and "only directories" mean... both + files = true; + directories = true; + } + + if (!base.canRead()) { + return null; + } + + List outgoing = new ArrayList<>(); + listFilesImpl(base, recursive, extensions, hidden, directories, files, outgoing); + return outgoing.toArray(new File[0]); + } + + + static void listFilesImpl(File folder, boolean recursive, + String[] extensions, boolean hidden, + boolean directories, boolean files, + List list) { + File[] items = folder.listFiles(); + if (items != null) { + for (File item : items) { + String name = item.getName(); + if (!hidden && name.charAt(0) == '.') { + continue; + } + if (item.isDirectory()) { + if (recursive) { + listFilesImpl(item, recursive, extensions, hidden, directories, files, list); + } + if (directories) { + list.add(item); + } + } else if (files) { + if (extensions == null) { + list.add(item); + } else { + for (String ext : extensions) { + if (item.getName().toLowerCase().endsWith(ext)) { + list.add(item); + } + } + } + } + } + } + } + + + + ////////////////////////////////////////////////////////////// + + // EXTENSIONS + + + /** + * Get the compression-free extension for this filename. + * @param filename The filename to check + * @return an extension, skipping past .gz if it's present + */ + static public String checkExtension(String filename) { + // Don't consider the .gz as part of the name, createInput() + // and createOuput() will take care of fixing that up. + if (filename.toLowerCase().endsWith(".gz")) { + filename = filename.substring(0, filename.length() - 3); + } + int dotIndex = filename.lastIndexOf('.'); + if (dotIndex != -1) { + return filename.substring(dotIndex + 1).toLowerCase(); + } + return null; + } + ////////////////////////////////////////////////////////////// @@ -4600,11 +4759,22 @@ static public BufferedReader createReader(File file) { * following lines any more I'm gonna send Sun my medical bills. */ static public BufferedReader createReader(InputStream input) { - InputStreamReader isr = null; + InputStreamReader isr = + new InputStreamReader(input, CompatUtils.getCharsetUTF8()); + + BufferedReader reader = new BufferedReader(isr); + // consume the Unicode BOM (byte order marker) if present try { - isr = new InputStreamReader(input, "UTF-8"); - } catch (UnsupportedEncodingException e) { } // not gonna happen - return new BufferedReader(isr); + reader.mark(1); + int c = reader.read(); + // if not the BOM, back up to the beginning again + if (c != '\uFEFF') { + reader.reset(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return reader; } @@ -4646,12 +4816,10 @@ static public PrintWriter createWriter(File file) { * It's the JavaSoft API engineers who need to explain themselves. */ static public PrintWriter createWriter(OutputStream output) { - try { - BufferedOutputStream bos = new BufferedOutputStream(output, 8192); - OutputStreamWriter osw = new OutputStreamWriter(bos, "UTF-8"); - return new PrintWriter(osw); - } catch (UnsupportedEncodingException e) { } // not gonna happen - return null; + BufferedOutputStream bos = new BufferedOutputStream(output, 8192); + OutputStreamWriter osw = + new OutputStreamWriter(bos, CompatUtils.getCharsetUTF8()); + return new PrintWriter(osw); } @@ -4693,15 +4861,18 @@ static public PrintWriter createWriter(OutputStream output) { */ public InputStream createInput(String filename) { InputStream input = createInputRaw(filename); - if ((input != null) && filename.toLowerCase().endsWith(".gz")) { + final String lower = filename.toLowerCase(); + if ((input != null) && + (lower.endsWith(".gz") || lower.endsWith(".svgz"))) { try { - return new GZIPInputStream(input); + // buffered has to go *around* the GZ, otherwise 25x slower + return new BufferedInputStream(new GZIPInputStream(input)); } catch (IOException e) { - e.printStackTrace(); + printStackTrace(e); return null; } } - return input; + return new BufferedInputStream(input); } @@ -4731,12 +4902,19 @@ public InputStream createInputRaw(String filename) { // URL url = new URL(filename); // stream = url.openStream(); // return stream; - HttpGet httpRequest = null; - httpRequest = new HttpGet(URI.create(filename)); - HttpClient httpclient = new DefaultHttpClient(); - HttpResponse response = (HttpResponse) httpclient.execute(httpRequest); - HttpEntity entity = response.getEntity(); - return entity.getContent(); + URL url = new URL(filename); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setDoInput(true); + con.connect(); + return con.getInputStream(); + //The following code is deprecaded by Android +// HttpGet httpRequest = null; +// httpRequest = new HttpGet(URI.create(filename)); +// HttpClient httpclient = new DefaultHttpClient(); +// HttpResponse response = (HttpResponse) httpclient.execute(httpRequest); +// HttpEntity entity = response.getEntity(); +// return entity.getContent(); // can't use BufferedHttpEntity because it may try to allocate a byte // buffer of the size of the download, bad when DL is 25 MB... [0200] // BufferedHttpEntity bufHttpEntity = new BufferedHttpEntity(entity); @@ -4751,7 +4929,7 @@ public InputStream createInputRaw(String filename) { } catch (IOException e) { // changed for 0117, shouldn't be throwing exception - e.printStackTrace(); + printStackTrace(e); //System.err.println("Error downloading from URL " + filename); return null; //throw new RuntimeException("Error downloading from URL " + filename); @@ -4825,7 +5003,7 @@ public InputStream createInputRaw(String filename) { */ // Try the assets folder - AssetManager assets = getAssets(); + AssetManager assets = surface.getAssets(); try { stream = assets.open(filename); if (stream != null) { @@ -4863,19 +5041,18 @@ public InputStream createInputRaw(String filename) { } // Attempt to load the file more directly. Doesn't like paths. - Context context = getApplicationContext(); - try { - // MODE_PRIVATE is default, should we use something else? - stream = context.openFileInput(filename); - if (stream != null) { - return stream; - } - } catch (FileNotFoundException e) { - // ignore this and move on - //e.printStackTrace(); - } +// try { +// // MODE_PRIVATE is default, should we use something else? +// stream = surface.openFileInput(filename); +// if (stream != null) { +// return stream; +// } +// } catch (FileNotFoundException e) { +// // ignore this and move on +// //e.printStackTrace(); +// } - return null; + return surface.openFileInput(filename); } @@ -4886,9 +5063,9 @@ static public InputStream createInput(File file) { try { InputStream input = new FileInputStream(file); if (file.getName().toLowerCase().endsWith(".gz")) { - return new GZIPInputStream(input); + return new BufferedInputStream(new GZIPInputStream(input)); } - return input; + return new BufferedInputStream(input); } catch (IOException e) { System.err.println("Could not createInput() for " + file); @@ -5073,7 +5250,7 @@ public OutputStream createOutput(String filename) { return fos; } catch (IOException e) { - e.printStackTrace(); + printStackTrace(e); } return null; } @@ -5081,11 +5258,12 @@ public OutputStream createOutput(String filename) { static public OutputStream createOutput(File file) { try { - FileOutputStream fos = new FileOutputStream(file); + createPath(file); // make sure the path exists + OutputStream output = new FileOutputStream(file); if (file.getName().toLowerCase().endsWith(".gz")) { - return new GZIPOutputStream(fos); + return new BufferedOutputStream(new GZIPOutputStream(output)); } - return fos; + return new BufferedOutputStream(output); } catch (IOException e) { e.printStackTrace(); @@ -5120,33 +5298,25 @@ public boolean saveStream(String targetFilename, InputStream sourceStream) { } - static public boolean saveStream(File targetFile, InputStream sourceStream) { + static public boolean saveStream(File target, InputStream source) { File tempFile = null; try { - File parentDir = targetFile.getParentFile(); - createPath(targetFile); - tempFile = File.createTempFile(targetFile.getName(), null, parentDir); - - BufferedInputStream bis = new BufferedInputStream(sourceStream, 16384); - FileOutputStream fos = new FileOutputStream(tempFile); - BufferedOutputStream bos = new BufferedOutputStream(fos); - - byte[] buffer = new byte[8192]; - int bytesRead; - while ((bytesRead = bis.read(buffer)) != -1) { - bos.write(buffer, 0, bytesRead); - } - - bos.flush(); - bos.close(); - bos = null; - - if (targetFile.exists() && !targetFile.delete()) { - System.err.println("Could not replace " + - targetFile.getAbsolutePath() + "."); + // make sure that this path actually exists before writing + createPath(target); + tempFile = createTempFile(target); + FileOutputStream targetStream = new FileOutputStream(tempFile); + + saveStream(targetStream, source); + targetStream.close(); + targetStream = null; + + if (target.exists()) { + if (!target.delete()) { + System.err.println("Could not replace " + + target.getAbsolutePath() + "."); + } } - - if (!tempFile.renameTo(targetFile)) { + if (!tempFile.renameTo(target)) { System.err.println("Could not rename temporary file " + tempFile.getAbsolutePath()); return false; @@ -5163,34 +5333,90 @@ static public boolean saveStream(File targetFile, InputStream sourceStream) { } - /** - * Saves bytes to a file to inside the sketch folder. + static public void saveStream(OutputStream target, + InputStream source) throws IOException { + BufferedInputStream bis = new BufferedInputStream(source, 16384); + BufferedOutputStream bos = new BufferedOutputStream(target); + + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = bis.read(buffer)) != -1) { + bos.write(buffer, 0, bytesRead); + } + + bos.flush(); + } + + + /** + * Saves bytes to a file to inside the sketch folder. * The filename can be a relative path, i.e. "poo/bytefun.txt" * would save to a file named "bytefun.txt" to a subfolder * called 'poo' inside the sketch folder. If the in-between * subfolders don't exist, they'll be created. */ - public void saveBytes(String filename, byte buffer[]) { - saveBytes(saveFile(filename), buffer); + public void saveBytes(String filename, byte[] data) { + saveBytes(saveFile(filename), data); + } + + + /** + * Creates a temporary file based on the name/extension of another file + * and in the same parent directory. Ensures that the same extension is used + * (i.e. so that .gz files are gzip compressed on output) and that it's done + * from the same directory so that renaming the file later won't cross file + * system boundaries. + */ + static private File createTempFile(File file) throws IOException { + File parentDir = file.getParentFile(); + String name = file.getName(); + String prefix; + String suffix = null; + int dot = name.lastIndexOf('.'); + if (dot == -1) { + prefix = name; + } else { + // preserve the extension so that .gz works properly + prefix = name.substring(0, dot); + suffix = name.substring(dot); + } + // Prefix must be three characters + if (prefix.length() < 3) { + prefix += "processing"; + } + return File.createTempFile(prefix, suffix, parentDir); } /** * Saves bytes to a specific File location specified by the user. */ - static public void saveBytes(File file, byte buffer[]) { + static public void saveBytes(File file, byte[] data) { + File tempFile = null; try { - String filename = file.getAbsolutePath(); - createPath(filename); - OutputStream output = new FileOutputStream(file); - if (file.getName().toLowerCase().endsWith(".gz")) { - output = new GZIPOutputStream(output); - } - saveBytes(output, buffer); + tempFile = createTempFile(file); + + OutputStream output = createOutput(tempFile); + saveBytes(output, data); output.close(); + output = null; + + if (file.exists()) { + if (!file.delete()) { + System.err.println("Could not replace " + file.getAbsolutePath()); + } + } + + if (!tempFile.renameTo(file)) { + System.err.println("Could not rename temporary file " + + tempFile.getAbsolutePath()); + } } catch (IOException e) { System.err.println("error saving bytes to " + file); + if (tempFile != null) { + tempFile.delete(); + } e.printStackTrace(); } } @@ -5199,9 +5425,9 @@ static public void saveBytes(File file, byte buffer[]) { /** * Spews a buffer of bytes to an OutputStream. */ - static public void saveBytes(OutputStream output, byte buffer[]) { + static public void saveBytes(OutputStream output, byte[] data) { try { - output.write(buffer); + output.write(data); output.flush(); } catch (IOException e) { @@ -5277,8 +5503,7 @@ public String sketchPath(String where) { if (new File(where).isAbsolute()) return where; } catch (Exception e) { } - Context context = getApplicationContext(); - return context.getFileStreamPath(where).getAbsolutePath(); + return surface.getFileStreamPath(where).getAbsolutePath(); } @@ -5321,20 +5546,31 @@ public File saveFile(String where) { /** * Return a full path to an item in the data folder. *

    - * In this method, the data path is defined not as the applet's actual - * data path, but a folder titled "data" in the sketch's working - * directory. When running inside the PDE, this will be the sketch's - * "data" folder. However, when exported (as application or applet), - * sketch's data folder is exported as part of the applications jar file, - * and it's not possible to read/write from the jar file in a generic way. - * If you need to read data from the jar file, you should use createInput(). + * The behavior of this function differs from the equivalent on the Java mode: files stored in + * the data folder of the sketch get packed as assets in the apk, and the path to the data folder + * is no longer valid. Only the name is needed to open them. However, if the file is not an asset, + * we can assume it has been created by the sketch, so it should have the sketch path. + * Discussed here: + * https://github.com/processing/processing-android/issues/450 */ public String dataPath(String where) { - // isAbsolute() could throw an access exception, but so will writing - // to the local disk using the sketch path, so this is safe here. - if (new File(where).isAbsolute()) return where; - - return sketchPath + File.separator + "data" + File.separator + where; + // First, we check if it is asset: + boolean isAsset = false; + AssetManager assets = surface.getAssets(); + InputStream is = null; + try { + is = assets.open(where); + isAsset = true; + } catch (IOException ex) { + //file does not exist + } finally { + try { + is.close(); + } catch (Exception ex) { } + } + if (isAsset) return where; + // Not an asset, let's just use sketch path: + return sketchPath(where); } @@ -5565,18 +5801,16 @@ static public int[] expand(int list[], int newSize) { return temp; } - - static public PImage[] expand(PImage list[]) { - return expand(list, list.length << 1); + static public long[] expand(long list[]) { + return expand(list, list.length > 0 ? list.length << 1 : 1); } - static public PImage[] expand(PImage list[], int newSize) { - PImage temp[] = new PImage[newSize]; + static public long[] expand(long list[], int newSize) { + long temp[] = new long[newSize]; System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); return temp; } - static public float[] expand(float list[]) { return expand(list, list.length << 1); } @@ -5587,6 +5821,15 @@ static public float[] expand(float list[], int newSize) { return temp; } + static public double[] expand(double list[]) { + return expand(list, list.length > 0 ? list.length << 1 : 1); + } + + static public double[] expand(double list[], int newSize) { + double temp[] = new double[newSize]; + System.arraycopy(list, 0, temp, 0, Math.min(newSize, list.length)); + return temp; + } static public String[] expand(String list[]) { return expand(list, list.length << 1); @@ -5881,6 +6124,15 @@ static public int[] subset(int list[], int start, int count) { return output; } + static public long[] subset(long[] list, int start) { + return subset(list, start, list.length - start); + } + + static public long[] subset(long[] list, int start, int count) { + long[] output = new long[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } static public float[] subset(float list[], int start) { return subset(list, start, list.length - start); @@ -5892,6 +6144,15 @@ static public float[] subset(float list[], int start, int count) { return output; } + static public double[] subset(double[] list, int start) { + return subset(list, start, list.length - start); + } + + static public double[] subset(double[] list, int start, int count) { + double[] output = new double[count]; + System.arraycopy(list, start, output, 0, count); + return output; + } static public String[] subset(String list[], int start) { return subset(list, start, list.length - start); @@ -6335,7 +6596,7 @@ static final public boolean parseBoolean(float what) { * @return true if 'what' is "true" or "TRUE", false otherwise */ static final public boolean parseBoolean(String what) { - return new Boolean(what).booleanValue(); + return Boolean.valueOf(what); } // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -6393,7 +6654,7 @@ static final public boolean[] parseBoolean(float what[]) { static final public boolean[] parseBoolean(String what[]) { boolean outgoing[] = new boolean[what.length]; for (int i = 0; i < what.length; i++) { - outgoing[i] = new Boolean(what[i]).booleanValue(); + outgoing[i] = Boolean.valueOf(what[i]); } return outgoing; } @@ -6682,7 +6943,7 @@ static final public float parseFloat(String what) { static final public float parseFloat(String what, float otherwise) { try { - return new Float(what).floatValue(); + return Float.valueOf(what); } catch (NumberFormatException e) { } return otherwise; @@ -6732,7 +6993,7 @@ static final public float[] parseFloat(String what[], float missing) { float output[] = new float[what.length]; for (int i = 0; i < what.length; i++) { try { - output[i] = new Float(what[i]).floatValue(); + output[i] = Float.valueOf(what[i]); } catch (NumberFormatException e) { output[i] = missing; } @@ -7221,12 +7482,20 @@ public final int color(float x, float y, float z, float a) { } + public int lerpColor(int c1, int c2, float amt) { + if (g != null) { + return g.lerpColor(c1, c2, amt); + } + // use the default mode (RGB) if lerpColor is called before setup() + return PGraphics.lerpColor(c1, c2, amt, RGB); + } + + static public int blendColor(int c1, int c2, int mode) { return PImage.blendColor(c1, c2, mode); } - ////////////////////////////////////////////////////////////// // MAIN @@ -7782,72 +8051,186 @@ public void updatePixels(int x1, int y1, int x2, int y2) { } - private void tellPDE(final String message) { - Log.i(getComponentName().getPackageName(), "PROCESSING " + message); + ////////////////////////////////////////////////////////////// + + // ANDROID-SPECIFIC API + + + // Wallpaper and wear API + + + public boolean wallpaperPreview() { + return surface.getEngine().isPreview(); } - @Override - protected void onStart() { - tellPDE("onStart"); - super.onStart(); + public float wallpaperOffset() { + return surface.getEngine().getXOffset(); } - @Override - protected void onStop() { - tellPDE("onStop"); - super.onStop(); + public int wallpaperHomeCount() { + float step = surface.getEngine().getXOffsetStep(); + if (0 < step) { + return (int)(1 + 1 / step); + } else { + return 1; + } } - ////////////////////////////////////////////////////////////// + public boolean wearAmbient() { + return surface.getEngine().isInAmbientMode(); + } + + + public boolean wearInteractive() { + return !surface.getEngine().isInAmbientMode(); + } + + + public boolean wearRound() { + return surface.getEngine().isRound(); + } + + + public boolean wearSquare() { + return !surface.getEngine().isRound(); + } + + + public Rect wearInsets() { + return surface.getEngine().getInsets(); + } + + + public boolean wearLowBit() { + return surface.getEngine().useLowBitAmbient(); + } + + + public boolean wearBurnIn() { + return surface.getEngine().requireBurnInProtection(); + } + + + // Ray casting API + + + public PVector[] getRayFromScreen(float screenX, float screenY, PVector[] ray) { + return g.getRayFromScreen(screenX, screenY, ray); + } + + + public void getRayFromScreen(float screenX, float screenY, PVector origin, PVector direction) { + g.getRayFromScreen(screenX, screenY, origin, direction); + } + + + public boolean intersectsSphere(float r, float screenX, float screenY) { + return g.intersectsSphere(r, screenX, screenY); + } + + + public boolean intersectsSphere(float r, PVector origin, PVector direction) { + return g.intersectsSphere(r, origin, direction); + } + + + public boolean intersectsBox(float w, float screenX, float screenY) { + return g.intersectsBox(w, screenX, screenY); + } + + + public boolean intersectsBox(float w, float h, float d, float screenX, float screenY) { + return g.intersectsBox(w, h, d, screenX, screenY); + } + + + public boolean intersectsBox(float size, PVector origin, PVector direction) { + return g.intersectsBox(size, origin, direction); + } + + + public boolean intersectsBox(float w, float h, float d, PVector origin, PVector direction) { + return g.intersectsBox(w, h, d, origin, direction); + } + + + public PVector intersectsPlane(float screenX, float screenY) { + return g.intersectsPlane(screenX, screenY); + } + + + public PVector intersectsPlane(PVector origin, PVector direction) { + return g.intersectsPlane(origin, direction); + } - // everything below this line is automatically generated. no touch. - // public functions for processing.core + public void eye() { + g.eye(); + } + + + public void calculate() { + } + /** - * Store data of some kind for the renderer that requires extra metadata of - * some kind. Usually this is a renderer-specific representation of the - * image data, for instance a BufferedImage with tint() settings applied for - * PGraphicsJava2D, or resized image data and OpenGL texture indices for - * PGraphicsOpenGL. - * @param renderer The PGraphics renderer associated to the image - * @param storage The metadata required by the renderer + * Sets the coordinate system in 3D centered at (width/2, height/2) + * and with the Y axis pointing up. */ - public void setCache(PImage image, Object storage) { - g.setCache(image, storage); + + public void cameraUp() { + g.cameraUp(); } /** - * Get cache storage data for the specified renderer. Because each renderer - * will cache data in different formats, it's necessary to store cache data - * keyed by the renderer object. Otherwise, attempting to draw the same - * image to both a PGraphicsJava2D and a PGraphicsOpenGL will cause errors. - * @param renderer The PGraphics renderer associated to the image - * @return metadata stored for the specified renderer + * Returns a copy of the current object matrix. + * Pass in null to create a new matrix. */ - public Object getCache(PImage image) { - return g.getCache(image); + public PMatrix3D getObjectMatrix() { + return g.getObjectMatrix(); } /** - * Remove information associated with this renderer from the cache, if any. - * @param renderer The PGraphics renderer whose cache data should be removed + * Copy the current object matrix into the specified target. + * Pass in null to create a new matrix. */ - public void removeCache(PImage image) { - g.removeCache(image); + public PMatrix3D getObjectMatrix(PMatrix3D target) { + return g.getObjectMatrix(target); } - public void flush() { - g.flush(); + /** + * Returns a copy of the current eye matrix. + * Pass in null to create a new matrix. + */ + public PMatrix3D getEyeMatrix() { + return g.getEyeMatrix(); + } + + + /** + * Copy the current eye matrix into the specified target. + * Pass in null to create a new matrix. + */ + public PMatrix3D getEyeMatrix(PMatrix3D target) { + return g.getEyeMatrix(target); } + ////////////////////////////////////////////////////////////// + + // EVERYTHING BELOW THIS LINE IS AUTOMATICALLY GENERATED. DO NOT TOUCH! + // This includes the Javadoc comments, which are automatically copied from + // the PImage and PGraphics source code files. + + // public functions for processing.core + + public PGL beginPGL() { return g.beginPGL(); } @@ -7858,24 +8241,11 @@ public void endPGL() { } - /** - * Enable a hint option. - *

    - * For the most part, hints are temporary api quirks, - * for which a proper api hasn't been properly worked out. - * for instance SMOOTH_IMAGES existed because smooth() - * wasn't yet implemented, but it will soon go away. - *

    - * They also exist for obscure features in the graphics - * engine, like enabling/disabling single pixel lines - * that ignore the zbuffer, the way they do in alphabot. - *

    - * Current hint options: - *

      - *
    • DISABLE_DEPTH_TEST - - * turns off the z-buffer in the P3D or OPENGL renderers. - *
    - */ + public void flush() { + g.flush(); + } + + public void hint(int which) { g.hint(which); } @@ -7890,26 +8260,41 @@ public void beginShape() { /** - * Start a new shape. - *

    - * Differences between beginShape() and line() and point() methods. - *

    - * beginShape() is intended to be more flexible at the expense of being - * a little more complicated to use. it handles more complicated shapes - * that can consist of many connected lines (so you get joins) or lines - * mixed with curves. - *

    - * The line() and point() command are for the far more common cases - * (particularly for our audience) that simply need to draw a line - * or a point on the screen. - *

    - * From the code side of things, line() may or may not call beginShape() - * to do the drawing. In the beta code, they do, but in the alpha code, - * they did not. they might be implemented one way or the other depending - * on tradeoffs of runtime efficiency vs. implementation efficiency &mdash - * meaning the speed that things run at vs. the speed it takes me to write - * the code and maintain it. for beta, the latter is most important so - * that's how things are implemented. + * ( begin auto-generated from beginShape.xml ) + * + * Using the beginShape() and endShape() functions allow + * creating more complex forms. beginShape() begins recording + * vertices for a shape and endShape() stops recording. The value of + * the MODE parameter tells it which types of shapes to create from + * the provided vertices. With no mode specified, the shape can be any + * irregular polygon. The parameters available for beginShape() are POINTS, + * LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, and QUAD_STRIP. + * After calling the beginShape() function, a series of + * vertex() commands must follow. To stop drawing the shape, call + * endShape(). The vertex() function with two parameters + * specifies a position in 2D and the vertex() function with three + * parameters specifies a position in 3D. Each shape will be outlined with + * the current stroke color and filled with the fill color. + *

    + * Transformations such as translate(), rotate(), and + * scale() do not work within beginShape(). It is also not + * possible to use other shapes, such as ellipse() or rect() + * within beginShape(). + *

    + * The P3D renderer settings allow stroke() and fill() + * settings to be altered per-vertex, however the default P2D renderer does + * not. Settings such as strokeWeight(), strokeCap(), and + * strokeJoin() cannot be changed while inside a + * beginShape()/endShape() block with any renderer. + * + * ( end auto-generated ) + * @webref shape:vertex + * @param kind Either POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, or QUAD_STRIP + * @see PShape + * @see PGraphics#endShape() + * @see PGraphics#vertex(float, float, float, float, float) + * @see PGraphics#curveVertex(float, float, float) + * @see PGraphics#bezierVertex(float, float, float, float, float, float, float, float, float) */ public void beginShape(int kind) { g.beginShape(kind); @@ -7926,40 +8311,116 @@ public void edge(boolean edge) { /** - * Sets the current normal vector. Only applies with 3D rendering - * and inside a beginShape/endShape block. - *

    - * This is for drawing three dimensional shapes and surfaces, - * allowing you to specify a vector perpendicular to the surface - * of the shape, which determines how lighting affects it. - *

    - * For people familiar with OpenGL, this function is basically - * identical to glNormal3f(). + * ( begin auto-generated from normal.xml ) + * + * Sets the current normal vector. This is for drawing three dimensional + * shapes and surfaces and specifies a vector perpendicular to the surface + * of the shape which determines how lighting affects it. Processing + * attempts to automatically assign normals to shapes, but since that's + * imperfect, this is a better option when you want more control. This + * function is identical to glNormal3f() in OpenGL. + * + * ( end auto-generated ) + * @webref lights_camera:lights + * @param nx x direction + * @param ny y direction + * @param nz z direction + * @see PGraphics#beginShape(int) + * @see PGraphics#endShape(int) + * @see PGraphics#lights() */ public void normal(float nx, float ny, float nz) { g.normal(nx, ny, nz); } + public void attribPosition(String name, float x, float y, float z) { + g.attribPosition(name, x, y, z); + } + + + public void attribNormal(String name, float nx, float ny, float nz) { + g.attribNormal(name, nx, ny, nz); + } + + + public void attribColor(String name, int color) { + g.attribColor(name, color); + } + + + public void attrib(String name, float... values) { + g.attrib(name, values); + } + + + public void attrib(String name, int... values) { + g.attrib(name, values); + } + + + public void attrib(String name, boolean... values) { + g.attrib(name, values); + } + + /** - * Set texture mode to either to use coordinates based on the IMAGE - * (more intuitive for new users) or NORMALIZED (better for advanced chaps) + * ( begin auto-generated from textureMode.xml ) + * + * Sets the coordinate space for texture mapping. There are two options, + * IMAGE, which refers to the actual coordinates of the image, and + * NORMAL, which refers to a normalized space of values ranging from 0 + * to 1. The default mode is IMAGE. In IMAGE, if an image is 100 x 200 + * pixels, mapping the image onto the entire size of a quad would require + * the points (0,0) (0,100) (100,200) (0,200). The same mapping in + * NORMAL_SPACE is (0,0) (0,1) (1,1) (0,1). + * + * ( end auto-generated ) + * @webref image:textures + * @param mode either IMAGE or NORMAL + * @see PGraphics#texture(PImage) + * @see PGraphics#textureWrap(int) */ public void textureMode(int mode) { g.textureMode(mode); } + /** + * ( begin auto-generated from textureWrap.xml ) + * + * Description to come... + * + * ( end auto-generated from textureWrap.xml ) + * + * @webref image:textures + * @param wrap Either CLAMP (default) or REPEAT + * @see PGraphics#texture(PImage) + * @see PGraphics#textureMode(int) + */ public void textureWrap(int wrap) { g.textureWrap(wrap); } /** - * Set texture image for current shape. - * Needs to be called between @see beginShape and @see endShape + * ( begin auto-generated from texture.xml ) * + * Sets a texture to be applied to vertex points. The texture() + * function must be called between beginShape() and + * endShape() and before any calls to vertex(). + *

    + * When textures are in use, the fill color is ignored. Instead, use tint() + * to specify the color of the texture as it is applied to the shape. + * + * ( end auto-generated ) + * @webref image:textures * @param image reference to a PImage object + * @see PGraphics#textureMode(int) + * @see PGraphics#textureWrap(int) + * @see PGraphics#beginShape(int) + * @see PGraphics#endShape(int) + * @see PGraphics#vertex(float, float, float, float, float) */ public void texture(PImage image) { g.texture(image); @@ -7968,7 +8429,7 @@ public void texture(PImage image) { /** * Removes texture image for current shape. - * Needs to be called between @see beginShape and @see endShape + * Needs to be called between beginShape and endShape * */ public void noTexture() { @@ -8001,22 +8462,54 @@ public void vertex(float x, float y, float u, float v) { } + /** + * ( begin auto-generated from vertex.xml ) + * + * All shapes are constructed by connecting a series of vertices. + * vertex() is used to specify the vertex coordinates for points, + * lines, triangles, quads, and polygons and is used exclusively within the + * beginShape() and endShape() function.
    + *
    + * Drawing a vertex in 3D using the z parameter requires the P3D + * parameter in combination with size as shown in the above example.
    + *
    + * This function is also used to map a texture onto the geometry. The + * texture() function declares the texture to apply to the geometry + * and the u and v coordinates set define the mapping of this + * texture to the form. By default, the coordinates used for u and + * v are specified in relation to the image's size in pixels, but + * this relation can be changed with textureMode(). + * + * ( end auto-generated ) + * @webref shape:vertex + * @param x x-coordinate of the vertex + * @param y y-coordinate of the vertex + * @param z z-coordinate of the vertex + * @param u horizontal coordinate for the texture mapping + * @param v vertical coordinate for the texture mapping + * @see PGraphics#beginShape(int) + * @see PGraphics#endShape(int) + * @see PGraphics#bezierVertex(float, float, float, float, float, float, float, float, float) + * @see PGraphics#quadraticVertex(float, float, float, float, float, float) + * @see PGraphics#curveVertex(float, float, float) + * @see PGraphics#texture(PImage) + */ public void vertex(float x, float y, float z, float u, float v) { g.vertex(x, y, z, u, v); } - /** This feature is in testing, do not use or rely upon its implementation */ - public void breakShape() { - g.breakShape(); - } - - + /** + * @webref shape:vertex + */ public void beginContour() { g.beginContour(); } + /** + * @webref shape:vertex + */ public void endContour() { g.endContour(); } @@ -8027,36 +8520,52 @@ public void endShape() { } + /** + * ( begin auto-generated from endShape.xml ) + * + * The endShape() function is the companion to beginShape() + * and may only be called after beginShape(). When endshape() + * is called, all of image data defined since the previous call to + * beginShape() is written into the image buffer. The constant CLOSE + * as the value for the MODE parameter to close the shape (to connect the + * beginning and the end). + * + * ( end auto-generated ) + * @webref shape:vertex + * @param mode use CLOSE to close the shape + * @see PShape + * @see PGraphics#beginShape(int) + */ public void endShape(int mode) { g.endShape(mode); } - public void clip(float a, float b, float c, float d) { - g.clip(a, b, c, d); - } - - - public void noClip() { - g.noClip(); - } - - - public void blendMode(int mode) { - g.blendMode(mode); - } - - + /** + * @webref shape + * @param filename name of file to load, can be .svg or .obj + * @see PShape + * @see PApplet#createShape() + */ public PShape loadShape(String filename) { return g.loadShape(filename); } - public PShape createShape(PShape source) { - return g.createShape(source); + /** + * @nowebref + */ + public PShape loadShape(String filename, String options) { + return g.loadShape(filename, options); } + /** + * @webref shape + * @see PShape + * @see PShape#endShape() + * @see PApplet#loadShape(String) + */ public PShape createShape() { return g.createShape(); } @@ -8067,48 +8576,137 @@ public PShape createShape(int type) { } + /** + * @param kind either POINT, LINE, TRIANGLE, QUAD, RECT, ELLIPSE, ARC, BOX, SPHERE + * @param p parameters that match the kind of shape + */ public PShape createShape(int kind, float... p) { return g.createShape(kind, p); } + /** + * ( begin auto-generated from loadShader.xml ) + * + * This is a new reference entry for Processing 2.0. It will be updated shortly. + * + * ( end auto-generated ) + * + * @webref rendering:shaders + * @param fragFilename name of fragment shader file + */ public PShader loadShader(String fragFilename) { return g.loadShader(fragFilename); } + /** + * @param vertFilename name of vertex shader file + */ public PShader loadShader(String fragFilename, String vertFilename) { return g.loadShader(fragFilename, vertFilename); } - public void shader(PShader shader) { + /** + * ( begin auto-generated from shader.xml ) + * + * This is a new reference entry for Processing 2.0. It will be updated shortly. + * + * ( end auto-generated ) + * + * @webref rendering:shaders + * @param shader name of shader file + */ + public void shader(PShader shader) { g.shader(shader); } + /** + * @param kind type of shader, either POINTS, LINES, or TRIANGLES + */ public void shader(PShader shader, int kind) { g.shader(shader, kind); } + /** + * ( begin auto-generated from resetShader.xml ) + * + * This is a new reference entry for Processing 2.0. It will be updated shortly. + * + * ( end auto-generated ) + * + * @webref rendering:shaders + */ public void resetShader() { g.resetShader(); } + /** + * @param kind type of shader, either POINTS, LINES, or TRIANGLES + */ public void resetShader(int kind) { g.resetShader(kind); } - public PShader getShader(int kind) { - return g.getShader(kind); + /** + * @param shader the fragment shader to apply + */ + public void filter(PShader shader) { + g.filter(shader); } - public void filter(PShader shader) { - g.filter(shader); + /** + * ( begin auto-generated from clip.xml ) + * + * Limits the rendering to the boundaries of a rectangle defined + * by the parameters. The boundaries are drawn based on the state + * of the imageMode() fuction, either CORNER, CORNERS, or CENTER. + * + * ( end auto-generated ) + * + * @webref rendering + * @param a x-coordinate of the rectangle, by default + * @param b y-coordinate of the rectangle, by default + * @param c width of the rectangle, by default + * @param d height of the rectangle, by default + */ + public void clip(float a, float b, float c, float d) { + g.clip(a, b, c, d); + } + + + /** + * ( begin auto-generated from noClip.xml ) + * + * Disables the clipping previously started by the clip() function. + * + * ( end auto-generated ) + * + * @webref rendering + */ + public void noClip() { + g.noClip(); + } + + + /** + * ( begin auto-generated from blendMode.xml ) + * + * This is a new reference entry for Processing 2.0. It will be updated shortly. + * + * ( end auto-generated ) + * + * @webref rendering + * @param mode the blending mode to use + */ + public void blendMode(int mode) { + g.blendMode(mode); } @@ -8119,6 +8717,36 @@ public void bezierVertex(float x2, float y2, } + /** + * ( begin auto-generated from bezierVertex.xml ) + * + * Specifies vertex coordinates for Bezier curves. Each call to + * bezierVertex() defines the position of two control points and one + * anchor point of a Bezier curve, adding a new segment to a line or shape. + * The first time bezierVertex() is used within a + * beginShape() call, it must be prefaced with a call to + * vertex() to set the first anchor point. This function must be + * used between beginShape() and endShape() and only when + * there is no MODE parameter specified to beginShape(). Using the + * 3D version requires rendering with P3D (see the Environment reference + * for more information). + * + * ( end auto-generated ) + * @webref shape:vertex + * @param x2 the x-coordinate of the 1st control point + * @param y2 the y-coordinate of the 1st control point + * @param z2 the z-coordinate of the 1st control point + * @param x3 the x-coordinate of the 2nd control point + * @param y3 the y-coordinate of the 2nd control point + * @param z3 the z-coordinate of the 2nd control point + * @param x4 the x-coordinate of the anchor point + * @param y4 the y-coordinate of the anchor point + * @param z4 the z-coordinate of the anchor point + * @see PGraphics#curveVertex(float, float, float) + * @see PGraphics#vertex(float, float, float, float, float) + * @see PGraphics#quadraticVertex(float, float, float, float, float, float) + * @see PGraphics#bezier(float, float, float, float, float, float, float, float, float, float, float, float) + */ public void bezierVertex(float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4) { @@ -8126,100 +8754,356 @@ public void bezierVertex(float x2, float y2, float z2, } + /** + * @webref shape:vertex + * @param cx the x-coordinate of the control point + * @param cy the y-coordinate of the control point + * @param x3 the x-coordinate of the anchor point + * @param y3 the y-coordinate of the anchor point + * @see PGraphics#curveVertex(float, float, float) + * @see PGraphics#vertex(float, float, float, float, float) + * @see PGraphics#bezierVertex(float, float, float, float, float, float) + * @see PGraphics#bezier(float, float, float, float, float, float, float, float, float, float, float, float) + */ public void quadraticVertex(float cx, float cy, float x3, float y3) { g.quadraticVertex(cx, cy, x3, y3); } + /** + * @param cz the z-coordinate of the control point + * @param z3 the z-coordinate of the anchor point + */ public void quadraticVertex(float cx, float cy, float cz, float x3, float y3, float z3) { g.quadraticVertex(cx, cy, cz, x3, y3, z3); } + /** + * ( begin auto-generated from curveVertex.xml ) + * + * Specifies vertex coordinates for curves. This function may only be used + * between beginShape() and endShape() and only when there is + * no MODE parameter specified to beginShape(). The first and last + * points in a series of curveVertex() lines will be used to guide + * the beginning and end of a the curve. A minimum of four points is + * required to draw a tiny curve between the second and third points. + * Adding a fifth point with curveVertex() will draw the curve + * between the second, third, and fourth points. The curveVertex() + * function is an implementation of Catmull-Rom splines. Using the 3D + * version requires rendering with P3D (see the Environment reference for + * more information). + * + * ( end auto-generated ) + * + * @webref shape:vertex + * @param x the x-coordinate of the vertex + * @param y the y-coordinate of the vertex + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#beginShape(int) + * @see PGraphics#endShape(int) + * @see PGraphics#vertex(float, float, float, float, float) + * @see PGraphics#bezier(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#quadraticVertex(float, float, float, float, float, float) + */ public void curveVertex(float x, float y) { g.curveVertex(x, y); } + /** + * @param z the z-coordinate of the vertex + */ public void curveVertex(float x, float y, float z) { g.curveVertex(x, y, z); } + /** + * ( begin auto-generated from point.xml ) + * + * Draws a point, a coordinate in space at the dimension of one pixel. The + * first parameter is the horizontal value for the point, the second value + * is the vertical value for the point, and the optional third value is the + * depth value. Drawing this shape in 3D with the z parameter + * requires the P3D parameter in combination with size() as shown in + * the above example. + * + * ( end auto-generated ) + * + * @webref shape:2d_primitives + * @param x x-coordinate of the point + * @param y y-coordinate of the point + * @see PGraphics#stroke(int) + */ public void point(float x, float y) { g.point(x, y); } + /** + * @param z z-coordinate of the point + */ public void point(float x, float y, float z) { g.point(x, y, z); } + /** + * ( begin auto-generated from line.xml ) + * + * Draws a line (a direct path between two points) to the screen. The + * version of line() with four parameters draws the line in 2D. To + * color a line, use the stroke() function. A line cannot be filled, + * therefore the fill() function will not affect the color of a + * line. 2D lines are drawn with a width of one pixel by default, but this + * can be changed with the strokeWeight() function. The version with + * six parameters allows the line to be placed anywhere within XYZ space. + * Drawing this shape in 3D with the z parameter requires the P3D + * parameter in combination with size() as shown in the above example. + * + * ( end auto-generated ) + * @webref shape:2d_primitives + * @param x1 x-coordinate of the first point + * @param y1 y-coordinate of the first point + * @param x2 x-coordinate of the second point + * @param y2 y-coordinate of the second point + * @see PGraphics#strokeWeight(float) + * @see PGraphics#strokeJoin(int) + * @see PGraphics#strokeCap(int) + * @see PGraphics#beginShape() + */ public void line(float x1, float y1, float x2, float y2) { g.line(x1, y1, x2, y2); } + /** + * @param z1 z-coordinate of the first point + * @param z2 z-coordinate of the second point + */ public void line(float x1, float y1, float z1, float x2, float y2, float z2) { g.line(x1, y1, z1, x2, y2, z2); } + /** + * ( begin auto-generated from triangle.xml ) + * + * A triangle is a plane created by connecting three points. The first two + * arguments specify the first point, the middle two arguments specify the + * second point, and the last two arguments specify the third point. + * + * ( end auto-generated ) + * @webref shape:2d_primitives + * @param x1 x-coordinate of the first point + * @param y1 y-coordinate of the first point + * @param x2 x-coordinate of the second point + * @param y2 y-coordinate of the second point + * @param x3 x-coordinate of the third point + * @param y3 y-coordinate of the third point + * @see PApplet#beginShape() + */ public void triangle(float x1, float y1, float x2, float y2, float x3, float y3) { g.triangle(x1, y1, x2, y2, x3, y3); } + /** + * ( begin auto-generated from quad.xml ) + * + * A quad is a quadrilateral, a four sided polygon. It is similar to a + * rectangle, but the angles between its edges are not constrained to + * ninety degrees. The first pair of parameters (x1,y1) sets the first + * vertex and the subsequent pairs should proceed clockwise or + * counter-clockwise around the defined shape. + * + * ( end auto-generated ) + * @webref shape:2d_primitives + * @param x1 x-coordinate of the first corner + * @param y1 y-coordinate of the first corner + * @param x2 x-coordinate of the second corner + * @param y2 y-coordinate of the second corner + * @param x3 x-coordinate of the third corner + * @param y3 y-coordinate of the third corner + * @param x4 x-coordinate of the fourth corner + * @param y4 y-coordinate of the fourth corner + */ public void quad(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { g.quad(x1, y1, x2, y2, x3, y3, x4, y4); } + /** + * ( begin auto-generated from rectMode.xml ) + * + * Modifies the location from which rectangles draw. The default mode is + * rectMode(CORNER), which specifies the location to be the upper + * left corner of the shape and uses the third and fourth parameters of + * rect() to specify the width and height. The syntax + * rectMode(CORNERS) uses the first and second parameters of + * rect() to set the location of one corner and uses the third and + * fourth parameters to set the opposite corner. The syntax + * rectMode(CENTER) draws the image from its center point and uses + * the third and forth parameters of rect() to specify the image's + * width and height. The syntax rectMode(RADIUS) draws the image + * from its center point and uses the third and forth parameters of + * rect() to specify half of the image's width and height. The + * parameter must be written in ALL CAPS because Processing is a case + * sensitive language. Note: In version 125, the mode named CENTER_RADIUS + * was shortened to RADIUS. + * + * ( end auto-generated ) + * @webref shape:attributes + * @param mode either CORNER, CORNERS, CENTER, or RADIUS + * @see PGraphics#rect(float, float, float, float) + */ public void rectMode(int mode) { g.rectMode(mode); } + /** + * ( begin auto-generated from rect.xml ) + * + * Draws a rectangle to the screen. A rectangle is a four-sided shape with + * every angle at ninety degrees. By default, the first two parameters set + * the location of the upper-left corner, the third sets the width, and the + * fourth sets the height. These parameters may be changed with the + * rectMode() function. + * + * ( end auto-generated ) + * + * @webref shape:2d_primitives + * @param a x-coordinate of the rectangle by default + * @param b y-coordinate of the rectangle by default + * @param c width of the rectangle by default + * @param d height of the rectangle by default + * @see PGraphics#rectMode(int) + * @see PGraphics#quad(float, float, float, float, float, float, float, float) + */ public void rect(float a, float b, float c, float d) { g.rect(a, b, c, d); } + /** + * @param r radii for all four corners + */ public void rect(float a, float b, float c, float d, float r) { g.rect(a, b, c, d, r); } + /** + * @param tl radius for top-left corner + * @param tr radius for top-right corner + * @param br radius for bottom-right corner + * @param bl radius for bottom-left corner + */ public void rect(float a, float b, float c, float d, float tl, float tr, float br, float bl) { g.rect(a, b, c, d, tl, tr, br, bl); } + /** + * ( begin auto-generated from square.xml ) + * + * Draws a square to the screen. A square is a four-sided shape with + * every angle at ninety degrees and each side is the same length. + * By default, the first two parameters set the location of the + * upper-left corner, the third sets the width and height. The way + * these parameters are interpreted, however, may be changed with the + * rectMode() function. + * + * ( end auto-generated ) + * + * @webref shape:2d_primitives + * @param x x-coordinate of the rectangle by default + * @param y y-coordinate of the rectangle by default + * @param extent width and height of the rectangle by default + * @see PGraphics#rect(float, float, float, float) + * @see PGraphics#rectMode(int) + */ + public void square(float x, float y, float extent) { + g.square(x, y, extent); + } + + + /** + * ( begin auto-generated from ellipseMode.xml ) + * + * The origin of the ellipse is modified by the ellipseMode() + * function. The default configuration is ellipseMode(CENTER), which + * specifies the location of the ellipse as the center of the shape. The + * RADIUS mode is the same, but the width and height parameters to + * ellipse() specify the radius of the ellipse, rather than the + * diameter. The CORNER mode draws the shape from the upper-left + * corner of its bounding box. The CORNERS mode uses the four + * parameters to ellipse() to set two opposing corners of the + * ellipse's bounding box. The parameter must be written in ALL CAPS + * because Processing is a case-sensitive language. + * + * ( end auto-generated ) + * @webref shape:attributes + * @param mode either CENTER, RADIUS, CORNER, or CORNERS + * @see PApplet#ellipse(float, float, float, float) + * @see PApplet#arc(float, float, float, float, float, float) + */ public void ellipseMode(int mode) { g.ellipseMode(mode); } + /** + * ( begin auto-generated from ellipse.xml ) + * + * Draws an ellipse (oval) in the display window. An ellipse with an equal + * width and height is a circle. The first two parameters set + * the location, the third sets the width, and the fourth sets the height. + * The origin may be changed with the ellipseMode() function. + * + * ( end auto-generated ) + * @webref shape:2d_primitives + * @param a x-coordinate of the ellipse + * @param b y-coordinate of the ellipse + * @param c width of the ellipse by default + * @param d height of the ellipse by default + * @see PApplet#ellipseMode(int) + * @see PApplet#arc(float, float, float, float, float, float) + */ public void ellipse(float a, float b, float c, float d) { g.ellipse(a, b, c, d); } /** - * Identical parameters and placement to ellipse, - * but draws only an arc of that ellipse. - *

    - * start and stop are always radians because angleMode() was goofy. - * ellipseMode() sets the placement. - *

    - * also tries to be smart about start < stop. + * ( begin auto-generated from arc.xml ) + * + * Draws an arc in the display window. Arcs are drawn along the outer edge + * of an ellipse defined by the x, y, width and + * height parameters. The origin or the arc's ellipse may be changed + * with the ellipseMode() function. The start and stop + * parameters specify the angles at which to draw the arc. + * + * ( end auto-generated ) + * @webref shape:2d_primitives + * @param a x-coordinate of the arc's ellipse + * @param b y-coordinate of the arc's ellipse + * @param c width of the arc's ellipse by default + * @param d height of the arc's ellipse by default + * @param start angle to start the arc, specified in radians + * @param stop angle to stop the arc, specified in radians + * @see PApplet#ellipse(float, float, float, float) + * @see PApplet#ellipseMode(int) + * @see PApplet#radians(float) + * @see PApplet#degrees(float) */ public void arc(float a, float b, float c, float d, float start, float stop) { @@ -8227,33 +9111,97 @@ public void arc(float a, float b, float c, float d, } + /* + * @param mode either OPEN, CHORD, or PIE + */ public void arc(float a, float b, float c, float d, float start, float stop, int mode) { g.arc(a, b, c, d, start, stop, mode); } + /** + * ( begin auto-generated from circle.xml ) + * + * Draws a circle to the screen. By default, the first two parameters + * set the location of the center, and the third sets the shape's width + * and height. The origin may be changed with the ellipseMode() + * function. + * + * ( end auto-generated ) + * @webref shape:2d_primitives + * @param x x-coordinate of the ellipse + * @param y y-coordinate of the ellipse + * @param extent width and height of the ellipse by default + * @see PApplet#ellipse(float, float, float, float) + * @see PApplet#ellipseMode(int) + */ + public void circle(float x, float y, float extent) { + g.circle(x, y, extent); + } + + + /** + * ( begin auto-generated from box.xml ) + * + * A box is an extruded rectangle. A box with equal dimension on all sides + * is a cube. + * + * ( end auto-generated ) + * + * @webref shape:3d_primitives + * @param size dimension of the box in all dimensions (creates a cube) + * @see PGraphics#sphere(float) + */ public void box(float size) { g.box(size); } + /** + * @param w dimension of the box in the x-dimension + * @param h dimension of the box in the y-dimension + * @param d dimension of the box in the z-dimension + */ public void box(float w, float h, float d) { g.box(w, h, d); } + /** + * ( begin auto-generated from sphereDetail.xml ) + * + * Controls the detail used to render a sphere by adjusting the number of + * vertices of the sphere mesh. The default resolution is 30, which creates + * a fairly detailed sphere definition with vertices every 360/30 = 12 + * degrees. If you're going to render a great number of spheres per frame, + * it is advised to reduce the level of detail using this function. The + * setting stays active until sphereDetail() is called again with a + * new parameter and so should not be called prior to every + * sphere() statement, unless you wish to render spheres with + * different settings, e.g. using less detail for smaller spheres or ones + * further away from the camera. To control the detail of the horizontal + * and vertical resolution independently, use the version of the functions + * with two parameters. + * + * ( end auto-generated ) + * + *

    Advanced

    + * Code for sphereDetail() submitted by toxi [031031]. + * Code for enhanced u/v version from davbol [080801]. + * + * @param res number of segments (minimum 3) used per full circle revolution + * @webref shape:3d_primitives + * @see PGraphics#sphere(float) + */ public void sphereDetail(int res) { g.sphereDetail(res); } /** - * Set the detail level for approximating a sphere. The ures and vres params - * control the horizontal and vertical resolution. - * - * Code for sphereDetail() submitted by toxi [031031]. - * Code for enhanced u/v version from davbol [080801]. + * @param ures number of segments used longitudinally per full circle revolutoin + * @param vres number of segments used latitudinally from top to bottom */ public void sphereDetail(int ures, int vres) { g.sphereDetail(ures, vres); @@ -8261,7 +9209,13 @@ public void sphereDetail(int ures, int vres) { /** - * Draw a sphere with radius r centered at coordinate 0, 0, 0. + * ( begin auto-generated from sphere.xml ) + * + * A sphere is a hollow ball made from tessellated triangles. + * + * ( end auto-generated ) + * + *

    Advanced

    *

    * Implementation notes: *

    @@ -8281,6 +9235,10 @@ public void sphereDetail(int ures, int vres) { * * [davbol 080801] now using separate sphereDetailU/V * + * + * @webref shape:3d_primitives + * @param r the radius of the sphere + * @see PGraphics#sphereDetail(int) */ public void sphere(float r) { g.sphere(r); @@ -8288,12 +9246,17 @@ public void sphere(float r) { /** - * Evalutes quadratic bezier at point t for points a, b, c, d. - * t varies between 0 and 1, and a and d are the on curve points, - * b and c are the control points. this can be done once with the - * x coordinates and a second time with the y coordinates to get - * the location of a bezier curve at t. - *

    + * ( begin auto-generated from bezierPoint.xml ) + * + * Evaluates the Bezier at point t for points a, b, c, d. The parameter t + * varies between 0 and 1, a and d are points on the curve, and b and c are + * the control points. This can be done once with the x coordinates and a + * second time with the y coordinates to get the location of a bezier curve + * at t. + * + * ( end auto-generated ) + * + *

    Advanced

    * For instance, to convert the following example:
        * stroke(255, 102, 0);
        * line(85, 20, 10, 10);
    @@ -8313,6 +9276,16 @@ public void sphere(float r) {
        *   vertex(x, y);
        * }
        * endShape();
    + * + * @webref shape:curves + * @param a coordinate of first point on the curve + * @param b coordinate of first control point + * @param c coordinate of second control point + * @param d coordinate of second point on the curve + * @param t value between 0 and 1 + * @see PGraphics#bezier(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#bezierVertex(float, float, float, float, float, float) + * @see PGraphics#curvePoint(float, float, float, float, float) */ public float bezierPoint(float a, float b, float c, float d, float t) { return g.bezierPoint(a, b, c, d, t); @@ -8320,20 +9293,74 @@ public float bezierPoint(float a, float b, float c, float d, float t) { /** - * Provide the tangent at the given point on the bezier curve. - * Fix from davbol for 0136. + * ( begin auto-generated from bezierTangent.xml ) + * + * Calculates the tangent of a point on a Bezier curve. There is a good + * definition of tangent on Wikipedia. + * + * ( end auto-generated ) + * + *

    Advanced

    + * Code submitted by Dave Bollinger (davol) for release 0136. + * + * @webref shape:curves + * @param a coordinate of first point on the curve + * @param b coordinate of first control point + * @param c coordinate of second control point + * @param d coordinate of second point on the curve + * @param t value between 0 and 1 + * @see PGraphics#bezier(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#bezierVertex(float, float, float, float, float, float) + * @see PGraphics#curvePoint(float, float, float, float, float) */ public float bezierTangent(float a, float b, float c, float d, float t) { return g.bezierTangent(a, b, c, d, t); } + /** + * ( begin auto-generated from bezierDetail.xml ) + * + * Sets the resolution at which Beziers display. The default value is 20. + * This function is only useful when using the P3D renderer as the default + * P2D renderer does not use this information. + * + * ( end auto-generated ) + * + * @webref shape:curves + * @param detail resolution of the curves + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#curveVertex(float, float, float) + * @see PGraphics#curveTightness(float) + */ public void bezierDetail(int detail) { g.bezierDetail(detail); } + public void bezier(float x1, float y1, + float x2, float y2, + float x3, float y3, + float x4, float y4) { + g.bezier(x1, y1, x2, y2, x3, y3, x4, y4); + } + + /** + * ( begin auto-generated from bezier.xml ) + * + * Draws a Bezier curve on the screen. These curves are defined by a series + * of anchor and control points. The first two parameters specify the first + * anchor point and the last two parameters specify the other anchor point. + * The middle parameters specify the control points which define the shape + * of the curve. Bezier curves were developed by French engineer Pierre + * Bezier. Using the 3D version requires rendering with P3D (see the + * Environment reference for more information). + * + * ( end auto-generated ) + * + *

    Advanced

    * Draw a cubic bezier curve. The first and last points are * the on-curve points. The middle two are the 'control' points, * or 'handles' in an application like Illustrator. @@ -8355,15 +9382,24 @@ public void bezierDetail(int detail) { * To draw a quadratic (instead of cubic) curve, * use the control point twice by doubling it: *
    bezier(x1, y1, cx, cy, cx, cy, x2, y2);
    + * + * @webref shape:curves + * @param x1 coordinates for the first anchor point + * @param y1 coordinates for the first anchor point + * @param z1 coordinates for the first anchor point + * @param x2 coordinates for the first control point + * @param y2 coordinates for the first control point + * @param z2 coordinates for the first control point + * @param x3 coordinates for the second control point + * @param y3 coordinates for the second control point + * @param z3 coordinates for the second control point + * @param x4 coordinates for the second anchor point + * @param y4 coordinates for the second anchor point + * @param z4 coordinates for the second anchor point + * + * @see PGraphics#bezierVertex(float, float, float, float, float, float) + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) */ - public void bezier(float x1, float y1, - float x2, float y2, - float x3, float y3, - float x4, float y4) { - g.bezier(x1, y1, x2, y2, x3, y3, x4, y4); - } - - public void bezier(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, @@ -8373,9 +9409,24 @@ public void bezier(float x1, float y1, float z1, /** - * Get a location along a catmull-rom curve segment. + * ( begin auto-generated from curvePoint.xml ) + * + * Evalutes the curve at point t for points a, b, c, d. The parameter t + * varies between 0 and 1, a and d are the control points, and b and c are + * the points on the curve. This can be done once with the x coordinates and a + * second time with the y coordinates to get the location of a curve at t. + * + * ( end auto-generated ) * - * @param t Value between zero and one for how far along the segment + * @webref shape:curves + * @param a coordinate of first control point + * @param b coordinate of first point on the curve + * @param c coordinate of second point on the curve + * @param d coordinate of second control point + * @param t value between 0 and 1 + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#curveVertex(float, float) + * @see PGraphics#bezierPoint(float, float, float, float, float) */ public float curvePoint(float a, float b, float c, float d, float t) { return g.curvePoint(a, b, c, d, t); @@ -8383,29 +9434,94 @@ public float curvePoint(float a, float b, float c, float d, float t) { /** - * Calculate the tangent at a t value (0..1) on a Catmull-Rom curve. + * ( begin auto-generated from curveTangent.xml ) + * + * Calculates the tangent of a point on a curve. There's a good definition + * of tangent on Wikipedia. + * + * ( end auto-generated ) + * + *

    Advanced

    * Code thanks to Dave Bollinger (Bug #715) + * + * @webref shape:curves + * @param a coordinate of first point on the curve + * @param b coordinate of first control point + * @param c coordinate of second control point + * @param d coordinate of second point on the curve + * @param t value between 0 and 1 + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#curveVertex(float, float) + * @see PGraphics#curvePoint(float, float, float, float, float) + * @see PGraphics#bezierTangent(float, float, float, float, float) */ public float curveTangent(float a, float b, float c, float d, float t) { return g.curveTangent(a, b, c, d, t); } + /** + * ( begin auto-generated from curveDetail.xml ) + * + * Sets the resolution at which curves display. The default value is 20. + * This function is only useful when using the P3D renderer as the default + * P2D renderer does not use this information. + * + * ( end auto-generated ) + * + * @webref shape:curves + * @param detail resolution of the curves + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#curveVertex(float, float) + * @see PGraphics#curveTightness(float) + */ public void curveDetail(int detail) { g.curveDetail(detail); } + /** + * ( begin auto-generated from curveTightness.xml ) + * + * Modifies the quality of forms created with curve() and + * curveVertex(). The parameter squishy determines how the + * curve fits to the vertex points. The value 0.0 is the default value for + * squishy (this value defines the curves to be Catmull-Rom splines) + * and the value 1.0 connects all the points with straight lines. Values + * within the range -5.0 and 5.0 will deform the curves but will leave them + * recognizable and as values increase in magnitude, they will continue to deform. + * + * ( end auto-generated ) + * + * @webref shape:curves + * @param tightness amount of deformation from the original vertices + * @see PGraphics#curve(float, float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#curveVertex(float, float) + */ public void curveTightness(float tightness) { g.curveTightness(tightness); } /** - * Draws a segment of Catmull-Rom curve. - *

    - * As of 0070, this function no longer doubles the first and - * last points. The curves are a bit more boring, but it's more + * ( begin auto-generated from curve.xml ) + * + * Draws a curved line on the screen. The first and second parameters + * specify the beginning control point and the last two parameters specify + * the ending control point. The middle parameters specify the start and + * stop of the curve. Longer curves can be created by putting a series of + * curve() functions together or using curveVertex(). An + * additional function called curveTightness() provides control for + * the visual quality of the curve. The curve() function is an + * implementation of Catmull-Rom splines. Using the 3D version requires + * rendering with P3D (see the Environment reference for more information). + * + * ( end auto-generated ) + * + *

    Advanced

    + * As of revision 0070, this function no longer doubles the first + * and last points. The curves are a bit more boring, but it's more * mathematically correct, and properly mirrored in curvePoint(). *

    * Identical to typing out:

    @@ -8416,6 +9532,19 @@ public void curveTightness(float tightness) {
        * curveVertex(x4, y4);
        * endShape();
        * 
    + * + * @webref shape:curves + * @param x1 coordinates for the beginning control point + * @param y1 coordinates for the beginning control point + * @param x2 coordinates for the first point + * @param y2 coordinates for the first point + * @param x3 coordinates for the second point + * @param y3 coordinates for the second point + * @param x4 coordinates for the ending control point + * @param y4 coordinates for the ending control point + * @see PGraphics#curveVertex(float, float) + * @see PGraphics#curveTightness(float) + * @see PGraphics#bezier(float, float, float, float, float, float, float, float, float, float, float, float) */ public void curve(float x1, float y1, float x2, float y2, @@ -8425,6 +9554,12 @@ public void curve(float x1, float y1, } + /** + * @param z1 coordinates for the beginning control point + * @param z2 coordinates for the first point + * @param z3 coordinates for the second point + * @param z4 coordinates for the ending control point + */ public void curve(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, @@ -8434,44 +9569,83 @@ public void curve(float x1, float y1, float z1, /** - * If true in PImage, use bilinear interpolation for copy() - * operations. When inherited by PGraphics, also controls shapes. + * ( begin auto-generated from imageMode.xml ) + * + * Modifies the location from which images draw. The default mode is + * imageMode(CORNER), which specifies the location to be the upper + * left corner and uses the fourth and fifth parameters of image() + * to set the image's width and height. The syntax + * imageMode(CORNERS) uses the second and third parameters of + * image() to set the location of one corner of the image and uses + * the fourth and fifth parameters to set the opposite corner. Use + * imageMode(CENTER) to draw images centered at the given x and y + * position.
    + *
    + * The parameter to imageMode() must be written in ALL CAPS because + * Processing is a case-sensitive language. + * + * ( end auto-generated ) + * + * @webref image:loading_displaying + * @param mode either CORNER, CORNERS, or CENTER + * @see PApplet#loadImage(String, String) + * @see PImage + * @see PGraphics#image(PImage, float, float, float, float) + * @see PGraphics#background(float, float, float, float) */ - public void smooth() { - g.smooth(); - } - - - public void smooth(int level) { - g.smooth(level); + public void imageMode(int mode) { + g.imageMode(mode); } /** - * Disable smoothing. See smooth(). + * ( begin auto-generated from image.xml ) + * + * Displays images to the screen. The images must be in the sketch's "data" + * directory to load correctly. Select "Add file..." from the "Sketch" menu + * to add the image. Processing currently works with GIF, JPEG, and Targa + * images. The img parameter specifies the image to display and the + * x and y parameters define the location of the image from + * its upper-left corner. The image is displayed at its original size + * unless the width and height parameters specify a different + * size.
    + *
    + * The imageMode() function changes the way the parameters work. For + * example, a call to imageMode(CORNERS) will change the + * width and height parameters to define the x and y values + * of the opposite corner of the image.
    + *
    + * The color of an image may be modified with the tint() function. + * This function will maintain transparency for GIF and PNG images. + * + * ( end auto-generated ) + * + *

    Advanced

    + * Starting with release 0124, when using the default (JAVA2D) renderer, + * smooth() will also improve image quality of resized images. + * + * @webref image:loading_displaying + * @param img the image to display + * @param a x-coordinate of the image by default + * @param b y-coordinate of the image by default + * @see PApplet#loadImage(String, String) + * @see PImage + * @see PGraphics#imageMode(int) + * @see PGraphics#tint(float) + * @see PGraphics#background(float, float, float, float) + * @see PGraphics#alpha(int) */ - public void noSmooth() { - g.noSmooth(); + public void image(PImage img, float a, float b) { + g.image(img, a, b); } /** - * The mode can only be set to CORNERS, CORNER, and CENTER. - *

    - * Support for CENTER was added in release 0146. + * @param c width to display the image by default + * @param d height to display the image by default */ - public void imageMode(int mode) { - g.imageMode(mode); - } - - - public void image(PImage image, float x, float y) { - g.image(image, x, y); - } - - - public void image(PImage image, float x, float y, float c, float d) { - g.image(image, x, y, c, d); + public void image(PImage img, float a, float b, float c, float d) { + g.image(img, a, b, c, d); } @@ -8479,17 +9653,38 @@ public void image(PImage image, float x, float y, float c, float d) { * Draw an image(), also specifying u/v coordinates. * In this method, the u, v coordinates are always based on image space * location, regardless of the current textureMode(). + * + * @nowebref */ - public void image(PImage image, + public void image(PImage img, float a, float b, float c, float d, int u1, int v1, int u2, int v2) { - g.image(image, a, b, c, d, u1, v1, u2, v2); + g.image(img, a, b, c, d, u1, v1, u2, v2); } /** - * Set the orientation for the shape() command (like imageMode() or rectMode()). - * @param mode Either CORNER, CORNERS, or CENTER. + * ( begin auto-generated from shapeMode.xml ) + * + * Modifies the location from which shapes draw. The default mode is + * shapeMode(CORNER), which specifies the location to be the upper + * left corner of the shape and uses the third and fourth parameters of + * shape() to specify the width and height. The syntax + * shapeMode(CORNERS) uses the first and second parameters of + * shape() to set the location of one corner and uses the third and + * fourth parameters to set the opposite corner. The syntax + * shapeMode(CENTER) draws the shape from its center point and uses + * the third and forth parameters of shape() to specify the width + * and height. The parameter must be written in "ALL CAPS" because + * Processing is a case sensitive language. + * + * ( end auto-generated ) + * + * @webref shape:loading_displaying + * @param mode either CORNER, CORNERS, CENTER + * @see PShape + * @see PGraphics#shape(PShape) + * @see PGraphics#rectMode(int) */ public void shapeMode(int mode) { g.shapeMode(mode); @@ -8502,6 +9697,33 @@ public void shape(PShape shape) { /** + * ( begin auto-generated from shape.xml ) + * + * Displays shapes to the screen. The shapes must be in the sketch's "data" + * directory to load correctly. Select "Add file..." from the "Sketch" menu + * to add the shape. Processing currently works with SVG shapes only. The + * sh parameter specifies the shape to display and the x and + * y parameters define the location of the shape from its upper-left + * corner. The shape is displayed at its original size unless the + * width and height parameters specify a different size. The + * shapeMode() function changes the way the parameters work. A call + * to shapeMode(CORNERS), for example, will change the width and + * height parameters to define the x and y values of the opposite corner of + * the shape. + *

    + * Note complex shapes may draw awkwardly with P3D. This renderer does not + * yet support shapes that have holes or complicated breaks. + * + * ( end auto-generated ) + * + * @webref shape:loading_displaying + * @param shape the shape to display + * @param x x-coordinate of the shape + * @param y y-coordinate of the shape + * @see PShape + * @see PApplet#loadShape(String) + * @see PGraphics#shapeMode(int) + * * Convenience method to draw at a particular location. */ public void shape(PShape shape, float x, float y) { @@ -8509,24 +9731,61 @@ public void shape(PShape shape, float x, float y) { } - public void shape(PShape shape, float x, float y, float c, float d) { - g.shape(shape, x, y, c, d); + /** + * @param a x-coordinate of the shape + * @param b y-coordinate of the shape + * @param c width to display the shape + * @param d height to display the shape + */ + public void shape(PShape shape, float a, float b, float c, float d) { + g.shape(shape, a, b, c, d); } - /** - * Sets the alignment of the text to one of LEFT, CENTER, or RIGHT. - * This will also reset the vertical text alignment to BASELINE. - */ - public void textAlign(int align) { - g.textAlign(align); + public void textAlign(int alignX) { + g.textAlign(alignX); } /** - * Sets the horizontal and vertical alignment of the text. The horizontal - * alignment can be one of LEFT, CENTER, or RIGHT. The vertical alignment - * can be TOP, BOTTOM, CENTER, or the BASELINE (the default). + * ( begin auto-generated from textAlign.xml ) + * + * Sets the current alignment for drawing text. The parameters LEFT, + * CENTER, and RIGHT set the display characteristics of the letters in + * relation to the values for the x and y parameters of the + * text() function. + *

    + * In Processing 0125 and later, an optional second parameter can be used + * to vertically align the text. BASELINE is the default, and the vertical + * alignment will be reset to BASELINE if the second parameter is not used. + * The TOP and CENTER parameters are straightforward. The BOTTOM parameter + * offsets the line based on the current textDescent(). For multiple + * lines, the final line will be aligned to the bottom, with the previous + * lines appearing above it. + *

    + * When using text() with width and height parameters, BASELINE is + * ignored, and treated as TOP. (Otherwise, text would by default draw + * outside the box, since BASELINE is the default setting. BASELINE is not + * a useful drawing mode for text drawn in a rectangle.) + *

    + * The vertical alignment is based on the value of textAscent(), + * which many fonts do not specify correctly. It may be necessary to use a + * hack and offset by a few pixels by hand so that the offset looks + * correct. To do this as less of a hack, use some percentage of + * textAscent() or textDescent() so that the hack works even + * if you change the size of the font. + * + * ( end auto-generated ) + * + * @webref typography:attributes + * @param alignX horizontal alignment, either LEFT, CENTER, or RIGHT + * @param alignY vertical alignment, either TOP, BOTTOM, CENTER, or BASELINE + * @see PApplet#loadFont(String) + * @see PFont + * @see PGraphics#text(String, float, float) + * @see PGraphics#textSize(float) + * @see PGraphics#textAscent() + * @see PGraphics#textDescent() */ public void textAlign(int alignX, int alignY) { g.textAlign(alignX, alignY); @@ -8534,9 +9793,17 @@ public void textAlign(int alignX, int alignY) { /** - * Returns the ascent of the current font at the current size. - * This is a method, rather than a variable inside the PGraphics object - * because it requires calculation. + * ( begin auto-generated from textAscent.xml ) + * + * Returns ascent of the current font at its current size. This information + * is useful for determining the height of the font above the baseline. For + * example, adding the textAscent() and textDescent() values + * will give you the total height of the line. + * + * ( end auto-generated ) + * + * @webref typography:metrics + * @see PGraphics#textDescent() */ public float textAscent() { return g.textAscent(); @@ -8544,9 +9811,17 @@ public float textAscent() { /** - * Returns the descent of the current font at the current size. - * This is a method, rather than a variable inside the PGraphics object - * because it requires calculation. + * ( begin auto-generated from textDescent.xml ) + * + * Returns descent of the current font at its current size. This + * information is useful for determining the height of the font below the + * baseline. For example, adding the textAscent() and + * textDescent() values will give you the total height of the line. + * + * ( end auto-generated ) + * + * @webref typography:metrics + * @see PGraphics#textAscent() */ public float textDescent() { return g.textDescent(); @@ -8554,9 +9829,33 @@ public float textDescent() { /** - * Sets the current font. The font's size will be the "natural" - * size of this font (the size that was set when using "Create Font"). - * The leading will also be reset. + * ( begin auto-generated from textFont.xml ) + * + * Sets the current font that will be drawn with the text() + * function. Fonts must be loaded with loadFont() before it can be + * used. This font will be used in all subsequent calls to the + * text() function. If no size parameter is input, the font + * will appear at its original size (the size it was created at with the + * "Create Font..." tool) until it is changed with textSize().

    Because fonts are usually bitmaped, you should create fonts at + * the sizes that will be used most commonly. Using textFont() + * without the size parameter will result in the cleanest-looking text.

    With the default (JAVA2D) and PDF renderers, it's also possible + * to enable the use of native fonts via the command + * hint(ENABLE_NATIVE_FONTS). This will produce vector text in + * JAVA2D sketches and PDF output in cases where the vector data is + * available: when the font is still installed, or the font is created via + * the createFont() function (rather than the Create Font tool). + * + * ( end auto-generated ) + * + * @webref typography:loading_displaying + * @param which any variable of the type PFont + * @see PApplet#createFont(String, float, boolean) + * @see PApplet#loadFont(String) + * @see PFont + * @see PGraphics#text(String, float, float) + * @see PGraphics#textSize(float) */ public void textFont(PFont which) { g.textFont(which); @@ -8564,7 +9863,7 @@ public void textFont(PFont which) { /** - * Useful function to set the font and size at the same time. + * @param size the size of the letters in units of pixels */ public void textFont(PFont which, float size) { g.textFont(which, size); @@ -8572,9 +9871,20 @@ public void textFont(PFont which, float size) { /** - * Set the text leading to a specific value. If using a custom - * value for the text leading, you'll have to call textLeading() - * again after any calls to textSize(). + * ( begin auto-generated from textLeading.xml ) + * + * Sets the spacing between lines of text in units of pixels. This setting + * will be used in all subsequent calls to the text() function. + * + * ( end auto-generated ) + * + * @webref typography:attributes + * @param leading the size in pixels for spacing between lines + * @see PApplet#loadFont(String) + * @see PFont#PFont + * @see PGraphics#text(String, float, float) + * @see PGraphics#textFont(PFont) + * @see PGraphics#textSize(float) */ public void textLeading(float leading) { g.textLeading(leading); @@ -8582,11 +9892,36 @@ public void textLeading(float leading) { /** - * Sets the text rendering/placement to be either SCREEN (direct - * to the screen, exact coordinates, only use the font's original size) - * or MODEL (the default, where text is manipulated by translate() and - * can have a textSize). The text size cannot be set when using - * textMode(SCREEN), because it uses the pixels directly from the font. + * ( begin auto-generated from textMode.xml ) + * + * Sets the way text draws to the screen. In the default configuration, the + * MODEL mode, it's possible to rotate, scale, and place letters in + * two and three dimensional space.
    + *
    + * The SHAPE mode draws text using the the glyph outlines of + * individual characters rather than as textures. This mode is only + * supported with the PDF and P3D renderer settings. With the + * PDF renderer, you must call textMode(SHAPE) before any + * other drawing occurs. If the outlines are not available, then + * textMode(SHAPE) will be ignored and textMode(MODEL) will + * be used instead.
    + *
    + * The textMode(SHAPE) option in P3D can be combined with + * beginRaw() to write vector-accurate text to 2D and 3D output + * files, for instance DXF or PDF. The SHAPE mode is + * not currently optimized for P3D, so if recording shape data, use + * textMode(MODEL) until you're ready to capture the geometry with beginRaw(). + * + * ( end auto-generated ) + * + * @webref typography:attributes + * @param mode either MODEL or SHAPE + * @see PApplet#loadFont(String) + * @see PFont#PFont + * @see PGraphics#text(String, float, float) + * @see PGraphics#textFont(PFont) + * @see PGraphics#beginRaw(PGraphics) + * @see PApplet#createFont(String, float, boolean) */ public void textMode(int mode) { g.textMode(mode); @@ -8594,21 +9929,47 @@ public void textMode(int mode) { /** - * Sets the text size, also resets the value for the leading. + * ( begin auto-generated from textSize.xml ) + * + * Sets the current font size. This size will be used in all subsequent + * calls to the text() function. Font size is measured in units of pixels. + * + * ( end auto-generated ) + * + * @webref typography:attributes + * @param size the size of the letters in units of pixels + * @see PApplet#loadFont(String) + * @see PFont#PFont + * @see PGraphics#text(String, float, float) + * @see PGraphics#textFont(PFont) */ public void textSize(float size) { g.textSize(size); } + /** + * @param c the character to measure + */ public float textWidth(char c) { return g.textWidth(c); } /** - * Return the width of a line of text. If the text has multiple - * lines, this returns the length of the longest line. + * ( begin auto-generated from textWidth.xml ) + * + * Calculates and returns the width of any character or text string. + * + * ( end auto-generated ) + * + * @webref typography:attributes + * @param str the String of characters to measure + * @see PApplet#loadFont(String) + * @see PFont#PFont + * @see PGraphics#text(String, float, float) + * @see PGraphics#textFont(PFont) + * @see PGraphics#textSize(float) */ public float textWidth(String str) { return g.textWidth(str); @@ -8616,9 +9977,47 @@ public float textWidth(String str) { /** - * Draw a single character on screen. - * Extremely slow when used with textMode(SCREEN) and Java 2D, - * because loadPixels has to be called first and updatePixels last. + * @nowebref + */ + public float textWidth(char[] chars, int start, int length) { + return g.textWidth(chars, start, length); + } + + + /** + * ( begin auto-generated from text.xml ) + * + * Draws text to the screen. Displays the information specified in the + * data or stringdata parameters on the screen in the + * position specified by the x and y parameters and the + * optional z parameter. A default font will be used unless a font + * is set with the textFont() function. Change the color of the text + * with the fill() function. The text displays in relation to the + * textAlign() function, which gives the option to draw to the left, + * right, and center of the coordinates. + *

    + * The x2 and y2 parameters define a rectangular area to + * display within and may only be used with string data. For text drawn + * inside a rectangle, the coordinates are interpreted based on the current + * rectMode() setting. + * + * ( end auto-generated ) + * + * @webref typography:loading_displaying + * @param c the alphanumeric character to be displayed + * @param x x-coordinate of text + * @param y y-coordinate of text + * @see PGraphics#textAlign(int, int) + * @see PGraphics#textFont(PFont) + * @see PGraphics#textMode(int) + * @see PGraphics#textSize(float) + * @see PGraphics#textLeading(float) + * @see PGraphics#textWidth(String) + * @see PGraphics#textAscent() + * @see PGraphics#textDescent() + * @see PGraphics#rectMode(int) + * @see PGraphics#fill(int, float) + * @see_external String */ public void text(char c, float x, float y) { g.text(c, x, y); @@ -8626,7 +10025,7 @@ public void text(char c, float x, float y) { /** - * Draw a single character on screen (with a z coordinate) + * @param z z-coordinate of text */ public void text(char c, float x, float y, float z) { g.text(c, x, y, z); @@ -8634,6 +10033,7 @@ public void text(char c, float x, float y, float z) { /** + *

    Advanced

    * Draw a chunk of text. * Newlines that are \n (Unix newline or linefeed char, ascii 10) * are honored, but \r (carriage return, Windows and Mac OS) are @@ -8644,6 +10044,20 @@ public void text(String str, float x, float y) { } + /** + *

    Advanced

    + * Method to draw text from an array of chars. This method will usually be + * more efficient than drawing from a String object, because the String will + * not be converted to a char array before drawing. + * @param chars the alphanumberic symbols to be displayed + * @param start array index at which to start writing characters + * @param stop array index at which to stop writing characters + */ + public void text(char[] chars, int start, int stop, float x, float y) { + g.text(chars, start, stop, x, y); + } + + /** * Same as above but with a z coordinate. */ @@ -8652,7 +10066,14 @@ public void text(String str, float x, float y, float z) { } + public void text(char[] chars, int start, int stop, + float x, float y, float z) { + g.text(chars, start, stop, x, y, z); + } + + /** + *

    Advanced

    * Draw text in a box that is constrained to a particular size. * The current rectMode() determines what the coordinates mean * (whether x1/y1/x2/y2 or x/y/w/h). @@ -8664,6 +10085,11 @@ public void text(String str, float x, float y, float z) { * Newlines that are \n (Unix newline or linefeed char, ascii 10) * are honored, and \r (carriage return, Windows and Mac OS) are * ignored. + * + * @param x1 by default, the x-coordinate of text, see rectMode() for more info + * @param y1 by default, the y-coordinate of text, see rectMode() for more info + * @param x2 by default, the width of the text box, see rectMode() for more info + * @param y2 by default, the height of the text box, see rectMode() for more info */ public void text(String str, float x1, float y1, float x2, float y2) { g.text(str, x1, y1, x2, y2); @@ -8686,6 +10112,8 @@ public void text(int num, float x, float y, float z) { * Users who want more control should use their own nf() cmmand, * or if they want the long, ugly version of float, * use String.valueOf() to convert the float to a String first. + * + * @param num the numeric value to be displayed */ public void text(float num, float x, float y) { g.text(num, x, y); @@ -8698,7 +10126,101 @@ public void text(float num, float x, float y, float z) { /** - * Push a copy of the current transformation matrix onto the stack. + * ( begin auto-generated from push.xml ) + * + * The push() function saves the current drawing style + * settings and transformations, while pop() restores these + * settings. Note that these functions are always used together. + * They allow you to change the style and transformation settings + * and later return to what you had. When a new state is started + * with push(), it builds on the current style and transform + * information.
    + *
    + * push() stores information related to the current + * transformation state and style settings controlled by the + * following functions: rotate(), translate(), + * scale(), fill(), stroke(), tint(), + * strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), + * colorMode(), textAlign(), textFont(), + * textMode(), textSize(), textLeading().
    + *
    + * The push() and pop() functions were added with + * Processing 3.5. They can be used in place of pushMatrix(), + * popMatrix(), pushStyles(), and popStyles(). + * The difference is that push() and pop() control both the + * transformations (rotate, scale, translate) and the drawing styles + * at the same time. + * + * ( end auto-generated ) + * + * @webref structure + * @see PGraphics#pop() + */ + public void push() { + g.push(); + } + + + /** + * ( begin auto-generated from pop.xml ) + * + * The pop() function restores the previous drawing style + * settings and transformations after push() has changed them. + * Note that these functions are always used together. They allow + * you to change the style and transformation settings and later + * return to what you had. When a new state is started with push(), + * it builds on the current style and transform information.
    + *
    + *
    + * push() stores information related to the current + * transformation state and style settings controlled by the + * following functions: rotate(), translate(), + * scale(), fill(), stroke(), tint(), + * strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), + * colorMode(), textAlign(), textFont(), + * textMode(), textSize(), textLeading().
    + *
    + * The push() and pop() functions were added with + * Processing 3.5. They can be used in place of pushMatrix(), + * popMatrix(), pushStyles(), and popStyles(). + * The difference is that push() and pop() control both the + * transformations (rotate, scale, translate) and the drawing styles + * at the same time. + * + * ( end auto-generated ) + * + * @webref structure + * @see PGraphics#push() + */ + public void pop() { + g.pop(); + } + + + /** + * ( begin auto-generated from pushMatrix.xml ) + * + * Pushes the current transformation matrix onto the matrix stack. + * Understanding pushMatrix() and popMatrix() requires + * understanding the concept of a matrix stack. The pushMatrix() + * function saves the current coordinate system to the stack and + * popMatrix() restores the prior coordinate system. + * pushMatrix() and popMatrix() are used in conjuction with + * the other transformation functions and may be embedded to control the + * scope of the transformations. + * + * ( end auto-generated ) + * + * @webref transform + * @see PGraphics#popMatrix() + * @see PGraphics#translate(float, float, float) + * @see PGraphics#scale(float) + * @see PGraphics#rotate(float) + * @see PGraphics#rotateX(float) + * @see PGraphics#rotateY(float) + * @see PGraphics#rotateZ(float) */ public void pushMatrix() { g.pushMatrix(); @@ -8706,7 +10228,20 @@ public void pushMatrix() { /** - * Replace the current transformation matrix with the top of the stack. + * ( begin auto-generated from popMatrix.xml ) + * + * Pops the current transformation matrix off the matrix stack. + * Understanding pushing and popping requires understanding the concept of + * a matrix stack. The pushMatrix() function saves the current + * coordinate system to the stack and popMatrix() restores the prior + * coordinate system. pushMatrix() and popMatrix() are used + * in conjuction with the other transformation functions and may be + * embedded to control the scope of the transformations. + * + * ( end auto-generated ) + * + * @webref transform + * @see PGraphics#pushMatrix() */ public void popMatrix() { g.popMatrix(); @@ -8714,29 +10249,77 @@ public void popMatrix() { /** - * Translate in X and Y. + * ( begin auto-generated from translate.xml ) + * + * Specifies an amount to displace objects within the display window. The + * x parameter specifies left/right translation, the y + * parameter specifies up/down translation, and the z parameter + * specifies translations toward/away from the screen. Using this function + * with the z parameter requires using P3D as a parameter in + * combination with size as shown in the above example. Transformations + * apply to everything that happens after and subsequent calls to the + * function accumulates the effect. For example, calling translate(50, + * 0) and then translate(20, 0) is the same as translate(70, + * 0). If translate() is called within draw(), the + * transformation is reset when the loop begins again. This function can be + * further controlled by the pushMatrix() and popMatrix(). + * + * ( end auto-generated ) + * + * @webref transform + * @param x left/right translation + * @param y up/down translation + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#rotate(float) + * @see PGraphics#rotateX(float) + * @see PGraphics#rotateY(float) + * @see PGraphics#rotateZ(float) + * @see PGraphics#scale(float, float, float) */ - public void translate(float tx, float ty) { - g.translate(tx, ty); + public void translate(float x, float y) { + g.translate(x, y); } /** - * Translate in X, Y, and Z. + * @param z forward/backward translation */ - public void translate(float tx, float ty, float tz) { - g.translate(tx, ty, tz); + public void translate(float x, float y, float z) { + g.translate(x, y, z); } /** - * Two dimensional rotation. + * ( begin auto-generated from rotate.xml ) + * + * Rotates a shape the amount specified by the angle parameter. + * Angles should be specified in radians (values from 0 to TWO_PI) or + * converted to radians with the radians() function. + *

    + * Objects are always rotated around their relative position to the origin + * and positive numbers rotate objects in a clockwise direction. + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * rotate(HALF_PI) and then rotate(HALF_PI) is the same as + * rotate(PI). All tranformations are reset when draw() + * begins again. + *

    + * Technically, rotate() multiplies the current transformation + * matrix by a rotation matrix. This function can be further controlled by + * the pushMatrix() and popMatrix(). * - * Same as rotateZ (this is identical to a 3D rotation along the z-axis) - * but included for clarity. It'd be weird for people drawing 2D graphics - * to be using rotateZ. And they might kick our a-- for the confusion. + * ( end auto-generated ) * - * Additional background. + * @webref transform + * @param angle angle of rotation specified in radians + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#rotateX(float) + * @see PGraphics#rotateY(float) + * @see PGraphics#rotateZ(float) + * @see PGraphics#scale(float, float, float) + * @see PApplet#radians(float) */ public void rotate(float angle) { g.rotate(angle); @@ -8744,7 +10327,32 @@ public void rotate(float angle) { /** - * Rotate around the X axis. + * ( begin auto-generated from rotateX.xml ) + * + * Rotates a shape around the x-axis the amount specified by the + * angle parameter. Angles should be specified in radians (values + * from 0 to PI*2) or converted to radians with the radians() + * function. Objects are always rotated around their relative position to + * the origin and positive numbers rotate objects in a counterclockwise + * direction. Transformations apply to everything that happens after and + * subsequent calls to the function accumulates the effect. For example, + * calling rotateX(PI/2) and then rotateX(PI/2) is the same + * as rotateX(PI). If rotateX() is called within the + * draw(), the transformation is reset when the loop begins again. + * This function requires using P3D as a third parameter to size() + * as shown in the example above. + * + * ( end auto-generated ) + * + * @webref transform + * @param angle angle of rotation specified in radians + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#rotate(float) + * @see PGraphics#rotateY(float) + * @see PGraphics#rotateZ(float) + * @see PGraphics#scale(float, float, float) + * @see PGraphics#translate(float, float, float) */ public void rotateX(float angle) { g.rotateX(angle); @@ -8752,7 +10360,32 @@ public void rotateX(float angle) { /** - * Rotate around the Y axis. + * ( begin auto-generated from rotateY.xml ) + * + * Rotates a shape around the y-axis the amount specified by the + * angle parameter. Angles should be specified in radians (values + * from 0 to PI*2) or converted to radians with the radians() + * function. Objects are always rotated around their relative position to + * the origin and positive numbers rotate objects in a counterclockwise + * direction. Transformations apply to everything that happens after and + * subsequent calls to the function accumulates the effect. For example, + * calling rotateY(PI/2) and then rotateY(PI/2) is the same + * as rotateY(PI). If rotateY() is called within the + * draw(), the transformation is reset when the loop begins again. + * This function requires using P3D as a third parameter to size() + * as shown in the examples above. + * + * ( end auto-generated ) + * + * @webref transform + * @param angle angle of rotation specified in radians + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#rotate(float) + * @see PGraphics#rotateX(float) + * @see PGraphics#rotateZ(float) + * @see PGraphics#scale(float, float, float) + * @see PGraphics#translate(float, float, float) */ public void rotateY(float angle) { g.rotateY(angle); @@ -8760,12 +10393,32 @@ public void rotateY(float angle) { /** - * Rotate around the Z axis. + * ( begin auto-generated from rotateZ.xml ) + * + * Rotates a shape around the z-axis the amount specified by the + * angle parameter. Angles should be specified in radians (values + * from 0 to PI*2) or converted to radians with the radians() + * function. Objects are always rotated around their relative position to + * the origin and positive numbers rotate objects in a counterclockwise + * direction. Transformations apply to everything that happens after and + * subsequent calls to the function accumulates the effect. For example, + * calling rotateZ(PI/2) and then rotateZ(PI/2) is the same + * as rotateZ(PI). If rotateZ() is called within the + * draw(), the transformation is reset when the loop begins again. + * This function requires using P3D as a third parameter to size() + * as shown in the examples above. * - * The functions rotate() and rotateZ() are identical, it's just that it make - * sense to have rotate() and then rotateX() and rotateY() when using 3D; - * nor does it make sense to use a function called rotateZ() if you're only - * doing things in 2D. so we just decided to have them both be the same. + * ( end auto-generated ) + * + * @webref transform + * @param angle angle of rotation specified in radians + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#rotate(float) + * @see PGraphics#rotateX(float) + * @see PGraphics#rotateY(float) + * @see PGraphics#scale(float, float, float) + * @see PGraphics#translate(float, float, float) */ public void rotateZ(float angle) { g.rotateZ(angle); @@ -8773,15 +10426,45 @@ public void rotateZ(float angle) { /** + *

    Advanced

    * Rotate about a vector in space. Same as the glRotatef() function. + * @nowebref + * @param x + * @param y + * @param z */ - public void rotate(float angle, float vx, float vy, float vz) { - g.rotate(angle, vx, vy, vz); + public void rotate(float angle, float x, float y, float z) { + g.rotate(angle, x, y, z); } /** - * Scale in all dimensions. + * ( begin auto-generated from scale.xml ) + * + * Increases or decreases the size of a shape by expanding and contracting + * vertices. Objects always scale from their relative origin to the + * coordinate system. Scale values are specified as decimal percentages. + * For example, the function call scale(2.0) increases the dimension + * of a shape by 200%. Transformations apply to everything that happens + * after and subsequent calls to the function multiply the effect. For + * example, calling scale(2.0) and then scale(1.5) is the + * same as scale(3.0). If scale() is called within + * draw(), the transformation is reset when the loop begins again. + * Using this fuction with the z parameter requires using P3D as a + * parameter for size() as shown in the example above. This function + * can be further controlled by pushMatrix() and popMatrix(). + * + * ( end auto-generated ) + * + * @webref transform + * @param s percentage to scale the object + * @see PGraphics#pushMatrix() + * @see PGraphics#popMatrix() + * @see PGraphics#translate(float, float, float) + * @see PGraphics#rotate(float) + * @see PGraphics#rotateX(float) + * @see PGraphics#rotateY(float) + * @see PGraphics#rotateZ(float) */ public void scale(float s) { g.scale(s); @@ -8789,18 +10472,22 @@ public void scale(float s) { /** + *

    Advanced

    * Scale in X and Y. Equivalent to scale(sx, sy, 1). * * Not recommended for use in 3D, because the z-dimension is just * scaled by 1, since there's no way to know what else to scale it by. + * + * @param x percentage to scale the object in the x-axis + * @param y percentage to scale the object in the y-axis */ - public void scale(float sx, float sy) { - g.scale(sx, sy); + public void scale(float x, float y) { + g.scale(x, y); } /** - * Scale in X, Y, and Z. + * @param z percentage to scale the object in the z-axis */ public void scale(float x, float y, float z) { g.scale(x, y, z); @@ -8808,7 +10495,33 @@ public void scale(float x, float y, float z) { /** - * Shear along X axis + * ( begin auto-generated from shearX.xml ) + * + * Shears a shape around the x-axis the amount specified by the + * angle parameter. Angles should be specified in radians (values + * from 0 to PI*2) or converted to radians with the radians() + * function. Objects are always sheared around their relative position to + * the origin and positive numbers shear objects in a clockwise direction. + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * shearX(PI/2) and then shearX(PI/2) is the same as + * shearX(PI). If shearX() is called within the + * draw(), the transformation is reset when the loop begins again. + *

    + * Technically, shearX() multiplies the current transformation + * matrix by a rotation matrix. This function can be further controlled by + * the pushMatrix() and popMatrix() functions. + * + * ( end auto-generated ) + * + * @webref transform + * @param angle angle of shear specified in radians + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#shearY(float) + * @see PGraphics#scale(float, float, float) + * @see PGraphics#translate(float, float, float) + * @see PApplet#radians(float) */ public void shearX(float angle) { g.shearX(angle); @@ -8816,7 +10529,33 @@ public void shearX(float angle) { /** - * Skew along Y axis + * ( begin auto-generated from shearY.xml ) + * + * Shears a shape around the y-axis the amount specified by the + * angle parameter. Angles should be specified in radians (values + * from 0 to PI*2) or converted to radians with the radians() + * function. Objects are always sheared around their relative position to + * the origin and positive numbers shear objects in a clockwise direction. + * Transformations apply to everything that happens after and subsequent + * calls to the function accumulates the effect. For example, calling + * shearY(PI/2) and then shearY(PI/2) is the same as + * shearY(PI). If shearY() is called within the + * draw(), the transformation is reset when the loop begins again. + *

    + * Technically, shearY() multiplies the current transformation + * matrix by a rotation matrix. This function can be further controlled by + * the pushMatrix() and popMatrix() functions. + * + * ( end auto-generated ) + * + * @webref transform + * @param angle angle of shear specified in radians + * @see PGraphics#popMatrix() + * @see PGraphics#pushMatrix() + * @see PGraphics#shearX(float) + * @see PGraphics#scale(float, float, float) + * @see PGraphics#translate(float, float, float) + * @see PApplet#radians(float) */ public void shearY(float angle) { g.shearY(angle); @@ -8824,13 +10563,41 @@ public void shearY(float angle) { /** - * Set the current transformation matrix to identity. + * ( begin auto-generated from resetMatrix.xml ) + * + * Replaces the current matrix with the identity matrix. The equivalent + * function in OpenGL is glLoadIdentity(). + * + * ( end auto-generated ) + * + * @webref transform + * @see PGraphics#pushMatrix() + * @see PGraphics#popMatrix() + * @see PGraphics#applyMatrix(PMatrix) + * @see PGraphics#printMatrix() */ public void resetMatrix() { g.resetMatrix(); } + /** + * ( begin auto-generated from applyMatrix.xml ) + * + * Multiplies the current matrix by the one specified through the + * parameters. This is very slow because it will try to calculate the + * inverse of the transform, so avoid it whenever possible. The equivalent + * function in OpenGL is glMultMatrix(). + * + * ( end auto-generated ) + * + * @webref transform + * @source + * @see PGraphics#pushMatrix() + * @see PGraphics#popMatrix() + * @see PGraphics#resetMatrix() + * @see PGraphics#printMatrix() + */ public void applyMatrix(PMatrix source) { g.applyMatrix(source); } @@ -8842,7 +10609,12 @@ public void applyMatrix(PMatrix2D source) { /** - * Apply a 3x2 affine transformation matrix. + * @param n00 numbers which define the 4x4 matrix to be multiplied + * @param n01 numbers which define the 4x4 matrix to be multiplied + * @param n02 numbers which define the 4x4 matrix to be multiplied + * @param n10 numbers which define the 4x4 matrix to be multiplied + * @param n11 numbers which define the 4x4 matrix to be multiplied + * @param n12 numbers which define the 4x4 matrix to be multiplied */ public void applyMatrix(float n00, float n01, float n02, float n10, float n11, float n12) { @@ -8856,7 +10628,16 @@ public void applyMatrix(PMatrix3D source) { /** - * Apply a 4x4 transformation matrix. + * @param n03 numbers which define the 4x4 matrix to be multiplied + * @param n13 numbers which define the 4x4 matrix to be multiplied + * @param n20 numbers which define the 4x4 matrix to be multiplied + * @param n21 numbers which define the 4x4 matrix to be multiplied + * @param n22 numbers which define the 4x4 matrix to be multiplied + * @param n23 numbers which define the 4x4 matrix to be multiplied + * @param n30 numbers which define the 4x4 matrix to be multiplied + * @param n31 numbers which define the 4x4 matrix to be multiplied + * @param n32 numbers which define the 4x4 matrix to be multiplied + * @param n33 numbers which define the 4x4 matrix to be multiplied */ public void applyMatrix(float n00, float n01, float n02, float n03, float n10, float n11, float n12, float n13, @@ -8914,28 +10695,113 @@ public void setMatrix(PMatrix3D source) { /** - * Print the current model (or "transformation") matrix. + * ( begin auto-generated from printMatrix.xml ) + * + * Prints the current matrix to the Console (the text window at the bottom + * of Processing). + * + * ( end auto-generated ) + * + * @webref transform + * @see PGraphics#pushMatrix() + * @see PGraphics#popMatrix() + * @see PGraphics#resetMatrix() + * @see PGraphics#applyMatrix(PMatrix) */ public void printMatrix() { g.printMatrix(); } + /** + * ( begin auto-generated from beginCamera.xml ) + * + * The beginCamera() and endCamera() functions enable + * advanced customization of the camera space. The functions are useful if + * you want to more control over camera movement, however for most users, + * the camera() function will be sufficient.

    The camera + * functions will replace any transformations (such as rotate() or + * translate()) that occur before them in draw(), but they + * will not automatically replace the camera transform itself. For this + * reason, camera functions should be placed at the beginning of + * draw() (so that transformations happen afterwards), and the + * camera() function can be used after beginCamera() if you + * want to reset the camera before applying transformations.

    This function sets the matrix mode to the camera matrix so calls such + * as translate(), rotate(), applyMatrix() and resetMatrix() + * affect the camera. beginCamera() should always be used with a + * following endCamera() and pairs of beginCamera() and + * endCamera() cannot be nested. + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + * @see PGraphics#camera() + * @see PGraphics#endCamera() + * @see PGraphics#applyMatrix(PMatrix) + * @see PGraphics#resetMatrix() + * @see PGraphics#translate(float, float, float) + * @see PGraphics#scale(float, float, float) + */ public void beginCamera() { g.beginCamera(); } + /** + * ( begin auto-generated from endCamera.xml ) + * + * The beginCamera() and endCamera() functions enable + * advanced customization of the camera space. Please see the reference for + * beginCamera() for a description of how the functions are used. + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + * @see PGraphics#beginCamera() + * @see PGraphics#camera(float, float, float, float, float, float, float, float, float) + */ public void endCamera() { g.endCamera(); } + /** + * ( begin auto-generated from camera.xml ) + * + * Sets the position of the camera through setting the eye position, the + * center of the scene, and which axis is facing upward. Moving the eye + * position and the direction it is pointing (the center of the scene) + * allows the images to be seen from different angles. The version without + * any parameters sets the camera to the default position, pointing to the + * center of the display window with the Y axis as up. The default values + * are camera(width/2.0, height/2.0, (height/2.0) / tan(PI*30.0 / + * 180.0), width/2.0, height/2.0, 0, 0, 1, 0). This function is similar + * to gluLookAt() in OpenGL, but it first clears the current camera settings. + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + * @see PGraphics#beginCamera() + * @see PGraphics#endCamera() + * @see PGraphics#frustum(float, float, float, float, float, float) + */ public void camera() { g.camera(); } + /** + * @param eyeX x-coordinate for the eye + * @param eyeY y-coordinate for the eye + * @param eyeZ z-coordinate for the eye + * @param centerX x-coordinate for the center of the scene + * @param centerY y-coordinate for the center of the scene + * @param centerZ z-coordinate for the center of the scene + * @param upX usually 0.0, 1.0, or -1.0 + * @param upY usually 0.0, 1.0, or -1.0 + * @param upZ usually 0.0, 1.0, or -1.0 + */ public void camera(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { @@ -8943,22 +10809,58 @@ public void camera(float eyeX, float eyeY, float eyeZ, } + /** + * ( begin auto-generated from printCamera.xml ) + * + * Prints the current camera matrix to the Console (the text window at the + * bottom of Processing). + * + * ( end auto-generated ) + * @webref lights_camera:camera + * @see PGraphics#camera(float, float, float, float, float, float, float, float, float) + */ public void printCamera() { g.printCamera(); } + /** + * ( begin auto-generated from ortho.xml ) + * + * Sets an orthographic projection and defines a parallel clipping volume. + * All objects with the same dimension appear the same size, regardless of + * whether they are near or far from the camera. The parameters to this + * function specify the clipping volume where left and right are the + * minimum and maximum x values, top and bottom are the minimum and maximum + * y values, and near and far are the minimum and maximum z values. If no + * parameters are given, the default is used: ortho(0, width, 0, height, + * -10, 10). + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + */ public void ortho() { g.ortho(); } + /** + * @param left left plane of the clipping volume + * @param right right plane of the clipping volume + * @param bottom bottom plane of the clipping volume + * @param top top plane of the clipping volume + */ public void ortho(float left, float right, float bottom, float top) { g.ortho(left, right, bottom, top); } + /** + * @param near maximum distance from the origin to the viewer + * @param far maximum distance from the origin away from the viewer + */ public void ortho(float left, float right, float bottom, float top, float near, float far) { @@ -8966,16 +10868,61 @@ public void ortho(float left, float right, } + /** + * ( begin auto-generated from perspective.xml ) + * + * Sets a perspective projection applying foreshortening, making distant + * objects appear smaller than closer ones. The parameters define a viewing + * volume with the shape of truncated pyramid. Objects near to the front of + * the volume appear their actual size, while farther objects appear + * smaller. This projection simulates the perspective of the world more + * accurately than orthographic projection. The version of perspective + * without parameters sets the default perspective and the version with + * four parameters allows the programmer to set the area precisely. The + * default values are: perspective(PI/3.0, width/height, cameraZ/10.0, + * cameraZ*10.0) where cameraZ is ((height/2.0) / tan(PI*60.0/360.0)); + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + */ public void perspective() { g.perspective(); } + /** + * @param fovy field-of-view angle (in radians) for vertical direction + * @param aspect ratio of width to height + * @param zNear z-position of nearest clipping plane + * @param zFar z-position of farthest clipping plane + */ public void perspective(float fovy, float aspect, float zNear, float zFar) { g.perspective(fovy, aspect, zNear, zFar); } + /** + * ( begin auto-generated from frustum.xml ) + * + * Sets a perspective matrix defined through the parameters. Works like + * glFrustum, except it wipes out the current perspective matrix rather + * than muliplying itself with it. + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + * @param left left coordinate of the clipping plane + * @param right right coordinate of the clipping plane + * @param bottom bottom coordinate of the clipping plane + * @param top top coordinate of the clipping plane + * @param near near component of the clipping plane; must be greater than zero + * @param far far component of the clipping plane; must be greater than the near value + * @see PGraphics#camera(float, float, float, float, float, float, float, float, float) + * @see PGraphics#beginCamera() + * @see PGraphics#endCamera() + * @see PGraphics#perspective(float, float, float, float) + */ public void frustum(float left, float right, float bottom, float top, float near, float far) { @@ -8983,15 +10930,35 @@ public void frustum(float left, float right, } + /** + * ( begin auto-generated from printProjection.xml ) + * + * Prints the current projection matrix to the Console (the text window at + * the bottom of Processing). + * + * ( end auto-generated ) + * + * @webref lights_camera:camera + * @see PGraphics#camera(float, float, float, float, float, float, float, float, float) + */ public void printProjection() { g.printProjection(); } /** - * Given an x and y coordinate, returns the x position of where - * that point would be placed on screen, once affected by translate(), - * scale(), or any other transformations. + * ( begin auto-generated from screenX.xml ) + * + * Takes a three-dimensional X, Y, Z position and returns the X value for + * where it will appear on a (two-dimensional) screen. + * + * ( end auto-generated ) + * + * @webref lights_camera:coordinates + * @param x 3D x-coordinate to be mapped + * @param y 3D y-coordinate to be mapped + * @see PGraphics#screenY(float, float, float) + * @see PGraphics#screenZ(float, float, float) */ public float screenX(float x, float y) { return g.screenX(x, y); @@ -8999,9 +10966,18 @@ public float screenX(float x, float y) { /** - * Given an x and y coordinate, returns the y position of where - * that point would be placed on screen, once affected by translate(), - * scale(), or any other transformations. + * ( begin auto-generated from screenY.xml ) + * + * Takes a three-dimensional X, Y, Z position and returns the Y value for + * where it will appear on a (two-dimensional) screen. + * + * ( end auto-generated ) + * + * @webref lights_camera:coordinates + * @param x 3D x-coordinate to be mapped + * @param y 3D y-coordinate to be mapped + * @see PGraphics#screenX(float, float, float) + * @see PGraphics#screenZ(float, float, float) */ public float screenY(float x, float y) { return g.screenY(x, y); @@ -9009,11 +10985,7 @@ public float screenY(float x, float y) { /** - * Maps a three dimensional point to its placement on-screen. - *

    - * Given an (x, y, z) coordinate, returns the x position of where - * that point would be placed on screen, once affected by translate(), - * scale(), or any other transformations. + * @param z 3D z-coordinate to be mapped */ public float screenX(float x, float y, float z) { return g.screenX(x, y, z); @@ -9021,11 +10993,7 @@ public float screenX(float x, float y, float z) { /** - * Maps a three dimensional point to its placement on-screen. - *

    - * Given an (x, y, z) coordinate, returns the y position of where - * that point would be placed on screen, once affected by translate(), - * scale(), or any other transformations. + * @param z 3D z-coordinate to be mapped */ public float screenY(float x, float y, float z) { return g.screenY(x, y, z); @@ -9033,15 +11001,19 @@ public float screenY(float x, float y, float z) { /** - * Maps a three dimensional point to its placement on-screen. - *

    - * Given an (x, y, z) coordinate, returns its z value. - * This value can be used to determine if an (x, y, z) coordinate - * is in front or in back of another (x, y, z) coordinate. - * The units are based on how the zbuffer is set up, and don't - * relate to anything "real". They're only useful for in - * comparison to another value obtained from screenZ(), - * or directly out of the zbuffer[]. + * ( begin auto-generated from screenZ.xml ) + * + * Takes a three-dimensional X, Y, Z position and returns the Z value for + * where it will appear on a (two-dimensional) screen. + * + * ( end auto-generated ) + * + * @webref lights_camera:coordinates + * @param x 3D x-coordinate to be mapped + * @param y 3D y-coordinate to be mapped + * @param z 3D z-coordinate to be mapped + * @see PGraphics#screenX(float, float, float) + * @see PGraphics#screenY(float, float, float) */ public float screenZ(float x, float y, float z) { return g.screenZ(x, y, z); @@ -9049,13 +11021,29 @@ public float screenZ(float x, float y, float z) { /** - * Returns the model space x value for an x, y, z coordinate. - *

    - * This will give you a coordinate after it has been transformed - * by translate(), rotate(), and camera(), but not yet transformed - * by the projection matrix. For instance, his can be useful for - * figuring out how points in 3D space relate to the edge - * coordinates of a shape. + * ( begin auto-generated from modelX.xml ) + * + * Returns the three-dimensional X, Y, Z position in model space. This + * returns the X value for a given coordinate based on the current set of + * transformations (scale, rotate, translate, etc.) The X value can be used + * to place an object in space relative to the location of the original + * point once the transformations are no longer in use. + *

    + * In the example, the modelX(), modelY(), and + * modelZ() functions record the location of a box in space after + * being placed using a series of translate and rotate commands. After + * popMatrix() is called, those transformations no longer apply, but the + * (x, y, z) coordinate returned by the model functions is used to place + * another box in the same location. + * + * ( end auto-generated ) + * + * @webref lights_camera:coordinates + * @param x 3D x-coordinate to be mapped + * @param y 3D y-coordinate to be mapped + * @param z 3D z-coordinate to be mapped + * @see PGraphics#modelY(float, float, float) + * @see PGraphics#modelZ(float, float, float) */ public float modelX(float x, float y, float z) { return g.modelX(x, y, z); @@ -9063,7 +11051,29 @@ public float modelX(float x, float y, float z) { /** - * Returns the model space y value for an x, y, z coordinate. + * ( begin auto-generated from modelY.xml ) + * + * Returns the three-dimensional X, Y, Z position in model space. This + * returns the Y value for a given coordinate based on the current set of + * transformations (scale, rotate, translate, etc.) The Y value can be used + * to place an object in space relative to the location of the original + * point once the transformations are no longer in use.
    + *
    + * In the example, the modelX(), modelY(), and + * modelZ() functions record the location of a box in space after + * being placed using a series of translate and rotate commands. After + * popMatrix() is called, those transformations no longer apply, but the + * (x, y, z) coordinate returned by the model functions is used to place + * another box in the same location. + * + * ( end auto-generated ) + * + * @webref lights_camera:coordinates + * @param x 3D x-coordinate to be mapped + * @param y 3D y-coordinate to be mapped + * @param z 3D z-coordinate to be mapped + * @see PGraphics#modelX(float, float, float) + * @see PGraphics#modelZ(float, float, float) */ public float modelY(float x, float y, float z) { return g.modelY(x, y, z); @@ -9071,18 +11081,79 @@ public float modelY(float x, float y, float z) { /** - * Returns the model space z value for an x, y, z coordinate. + * ( begin auto-generated from modelZ.xml ) + * + * Returns the three-dimensional X, Y, Z position in model space. This + * returns the Z value for a given coordinate based on the current set of + * transformations (scale, rotate, translate, etc.) The Z value can be used + * to place an object in space relative to the location of the original + * point once the transformations are no longer in use.
    + *
    + * In the example, the modelX(), modelY(), and + * modelZ() functions record the location of a box in space after + * being placed using a series of translate and rotate commands. After + * popMatrix() is called, those transformations no longer apply, but the + * (x, y, z) coordinate returned by the model functions is used to place + * another box in the same location. + * + * ( end auto-generated ) + * + * @webref lights_camera:coordinates + * @param x 3D x-coordinate to be mapped + * @param y 3D y-coordinate to be mapped + * @param z 3D z-coordinate to be mapped + * @see PGraphics#modelX(float, float, float) + * @see PGraphics#modelY(float, float, float) */ public float modelZ(float x, float y, float z) { return g.modelZ(x, y, z); } + /** + * ( begin auto-generated from pushStyle.xml ) + * + * The pushStyle() function saves the current style settings and + * popStyle() restores the prior settings. Note that these functions + * are always used together. They allow you to change the style settings + * and later return to what you had. When a new style is started with + * pushStyle(), it builds on the current style information. The + * pushStyle() and popStyle() functions can be embedded to + * provide more control (see the second example above for a demonstration.) + *

    + * The style information controlled by the following functions are included + * in the style: + * fill(), stroke(), tint(), strokeWeight(), strokeCap(), strokeJoin(), + * imageMode(), rectMode(), ellipseMode(), shapeMode(), colorMode(), + * textAlign(), textFont(), textMode(), textSize(), textLeading(), + * emissive(), specular(), shininess(), ambient() + * + * ( end auto-generated ) + * + * @webref structure + * @see PGraphics#popStyle() + */ public void pushStyle() { g.pushStyle(); } + /** + * ( begin auto-generated from popStyle.xml ) + * + * The pushStyle() function saves the current style settings and + * popStyle() restores the prior settings; these functions are + * always used together. They allow you to change the style settings and + * later return to what you had. When a new style is started with + * pushStyle(), it builds on the current style information. The + * pushStyle() and popStyle() functions can be embedded to + * provide more control (see the second example above for a demonstration.) + * + * ( end auto-generated ) + * + * @webref structure + * @see PGraphics#pushStyle() + */ public void popStyle() { g.popStyle(); } @@ -9093,40 +11164,153 @@ public void style(PStyle s) { } + /** + * ( begin auto-generated from strokeWeight.xml ) + * + * Sets the width of the stroke used for lines, points, and the border + * around shapes. All widths are set in units of pixels. + *

    + * When drawing with P3D, series of connected lines (such as the stroke + * around a polygon, triangle, or ellipse) produce unattractive results + * when a thick stroke weight is set (see + * Issue 123). With P3D, the minimum and maximum values for + * strokeWeight() are controlled by the graphics card and the + * operating system's OpenGL implementation. For instance, the thickness + * may not go higher than 10 pixels. + * + * ( end auto-generated ) + * + * @webref shape:attributes + * @param weight the weight (in pixels) of the stroke + * @see PGraphics#stroke(int, float) + * @see PGraphics#strokeJoin(int) + * @see PGraphics#strokeCap(int) + */ public void strokeWeight(float weight) { g.strokeWeight(weight); } + /** + * ( begin auto-generated from strokeJoin.xml ) + * + * Sets the style of the joints which connect line segments. These joints + * are either mitered, beveled, or rounded and specified with the + * corresponding parameters MITER, BEVEL, and ROUND. The default joint is + * MITER. + *

    + * This function is not available with the P3D renderer, (see + * Issue 123). More information about the renderers can be found in the + * size() reference. + * + * ( end auto-generated ) + * + * @webref shape:attributes + * @param join either MITER, BEVEL, ROUND + * @see PGraphics#stroke(int, float) + * @see PGraphics#strokeWeight(float) + * @see PGraphics#strokeCap(int) + */ public void strokeJoin(int join) { g.strokeJoin(join); } + /** + * ( begin auto-generated from strokeCap.xml ) + * + * Sets the style for rendering line endings. These ends are either + * squared, extended, or rounded and specified with the corresponding + * parameters SQUARE, PROJECT, and ROUND. The default cap is ROUND. + *

    + * This function is not available with the P3D renderer (see + * Issue 123). More information about the renderers can be found in the + * size() reference. + * + * ( end auto-generated ) + * + * @webref shape:attributes + * @param cap either SQUARE, PROJECT, or ROUND + * @see PGraphics#stroke(int, float) + * @see PGraphics#strokeWeight(float) + * @see PGraphics#strokeJoin(int) + * @see PApplet#size(int, int, String, String) + */ public void strokeCap(int cap) { g.strokeCap(cap); } + /** + * ( begin auto-generated from noStroke.xml ) + * + * Disables drawing the stroke (outline). If both noStroke() and + * noFill() are called, nothing will be drawn to the screen. + * + * ( end auto-generated ) + * + * @webref color:setting + * @see PGraphics#stroke(int, float) + * @see PGraphics#fill(float, float, float, float) + * @see PGraphics#noFill() + */ public void noStroke() { g.noStroke(); } /** - * Set the tint to either a grayscale or ARGB value. - * See notes attached to the fill() function. + * ( begin auto-generated from stroke.xml ) + * + * Sets the color used to draw lines and borders around shapes. This color + * is either specified in terms of the RGB or HSB color depending on the + * current colorMode() (the default color space is RGB, with each + * value in the range from 0 to 255). + *

    + * When using hexadecimal notation to specify a color, use "#" or "0x" + * before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six + * digits to specify a color (the way colors are specified in HTML and + * CSS). When using the hexadecimal notation starting with "0x", the + * hexadecimal value must be specified with eight characters; the first two + * characters define the alpha component and the remainder the red, green, + * and blue components. + *

    + * The value for the parameter "gray" must be less than or equal to the + * current maximum value as specified by colorMode(). The default + * maximum value is 255. + * + * ( end auto-generated ) + * + * @param rgb color value in hexadecimal notation + * @see PGraphics#noStroke() + * @see PGraphics#strokeWeight(float) + * @see PGraphics#strokeJoin(int) + * @see PGraphics#strokeCap(int) + * @see PGraphics#fill(int, float) + * @see PGraphics#noFill() + * @see PGraphics#tint(int, float) + * @see PGraphics#background(float, float, float, float) + * @see PGraphics#colorMode(int, float, float, float, float) */ public void stroke(int rgb) { g.stroke(rgb); } + /** + * @param alpha opacity of the stroke + */ public void stroke(int rgb, float alpha) { g.stroke(rgb, alpha); } + /** + * @param gray specifies a value between white and black + */ public void stroke(float gray) { g.stroke(gray); } @@ -9137,34 +11321,90 @@ public void stroke(float gray, float alpha) { } - public void stroke(float x, float y, float z) { - g.stroke(x, y, z); + /** + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + * @webref color:setting + */ + public void stroke(float v1, float v2, float v3) { + g.stroke(v1, v2, v3); } - public void stroke(float x, float y, float z, float a) { - g.stroke(x, y, z, a); + public void stroke(float v1, float v2, float v3, float alpha) { + g.stroke(v1, v2, v3, alpha); } + /** + * ( begin auto-generated from noTint.xml ) + * + * Removes the current fill value for displaying images and reverts to + * displaying images with their original hues. + * + * ( end auto-generated ) + * + * @webref image:loading_displaying + * @usage web_application + * @see PGraphics#tint(float, float, float, float) + * @see PGraphics#image(PImage, float, float, float, float) + */ public void noTint() { g.noTint(); } /** - * Set the tint to either a grayscale or ARGB value. + * ( begin auto-generated from tint.xml ) + * + * Sets the fill value for displaying images. Images can be tinted to + * specified colors or made transparent by setting the alpha.
    + *
    + * To make an image transparent, but not change it's color, use white as + * the tint color and specify an alpha value. For instance, tint(255, 128) + * will make an image 50% transparent (unless colorMode() has been + * used).
    + *
    + * When using hexadecimal notation to specify a color, use "#" or "0x" + * before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six + * digits to specify a color (the way colors are specified in HTML and + * CSS). When using the hexadecimal notation starting with "0x", the + * hexadecimal value must be specified with eight characters; the first two + * characters define the alpha component and the remainder the red, green, + * and blue components.
    + *
    + * The value for the parameter "gray" must be less than or equal to the + * current maximum value as specified by colorMode(). The default + * maximum value is 255.
    + *
    + * The tint() function is also used to control the coloring of + * textures in 3D. + * + * ( end auto-generated ) + * + * @webref image:loading_displaying + * @usage web_application + * @param rgb color value in hexadecimal notation + * @see PGraphics#noTint() + * @see PGraphics#image(PImage, float, float, float, float) */ public void tint(int rgb) { g.tint(rgb); } + /** + * @param alpha opacity of the image + */ public void tint(int rgb, float alpha) { g.tint(rgb, alpha); } + /** + * @param gray specifies a value between white and black + */ public void tint(float gray) { g.tint(gray); } @@ -9175,34 +11415,91 @@ public void tint(float gray, float alpha) { } - public void tint(float x, float y, float z) { - g.tint(x, y, z); + /** + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + */ + public void tint(float v1, float v2, float v3) { + g.tint(v1, v2, v3); } - public void tint(float x, float y, float z, float a) { - g.tint(x, y, z, a); + public void tint(float v1, float v2, float v3, float alpha) { + g.tint(v1, v2, v3, alpha); } + /** + * ( begin auto-generated from noFill.xml ) + * + * Disables filling geometry. If both noStroke() and noFill() + * are called, nothing will be drawn to the screen. + * + * ( end auto-generated ) + * + * @webref color:setting + * @usage web_application + * @see PGraphics#fill(float, float, float, float) + * @see PGraphics#stroke(int, float) + * @see PGraphics#noStroke() + */ public void noFill() { g.noFill(); } /** - * Set the fill to either a grayscale value or an ARGB int. + * ( begin auto-generated from fill.xml ) + * + * Sets the color used to fill shapes. For example, if you run fill(204, + * 102, 0), all subsequent shapes will be filled with orange. This + * color is either specified in terms of the RGB or HSB color depending on + * the current colorMode() (the default color space is RGB, with + * each value in the range from 0 to 255). + *

    + * When using hexadecimal notation to specify a color, use "#" or "0x" + * before the values (e.g. #CCFFAA, 0xFFCCFFAA). The # syntax uses six + * digits to specify a color (the way colors are specified in HTML and + * CSS). When using the hexadecimal notation starting with "0x", the + * hexadecimal value must be specified with eight characters; the first two + * characters define the alpha component and the remainder the red, green, + * and blue components. + *

    + * The value for the parameter "gray" must be less than or equal to the + * current maximum value as specified by colorMode(). The default + * maximum value is 255. + *

    + * To change the color of an image (or a texture), use tint(). + * + * ( end auto-generated ) + * + * @webref color:setting + * @usage web_application + * @param rgb color variable or hex value + * @see PGraphics#noFill() + * @see PGraphics#stroke(int, float) + * @see PGraphics#noStroke() + * @see PGraphics#tint(int, float) + * @see PGraphics#background(float, float, float, float) + * @see PGraphics#colorMode(int, float, float, float, float) */ public void fill(int rgb) { g.fill(rgb); } + /** + * @param alpha opacity of the fill + */ public void fill(int rgb, float alpha) { g.fill(rgb, alpha); } + /** + * @param gray number specifying value between white and black + */ public void fill(float gray) { g.fill(gray); } @@ -9213,127 +11510,465 @@ public void fill(float gray, float alpha) { } - public void fill(float x, float y, float z) { - g.fill(x, y, z); + /** + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + */ + public void fill(float v1, float v2, float v3) { + g.fill(v1, v2, v3); } - public void fill(float x, float y, float z, float a) { - g.fill(x, y, z, a); + public void fill(float v1, float v2, float v3, float alpha) { + g.fill(v1, v2, v3, alpha); } + /** + * ( begin auto-generated from ambient.xml ) + * + * Sets the ambient reflectance for shapes drawn to the screen. This is + * combined with the ambient light component of environment. The color + * components set through the parameters define the reflectance. For + * example in the default color mode, setting v1=255, v2=126, v3=0, would + * cause all the red light to reflect and half of the green light to + * reflect. Used in combination with emissive(), specular(), + * and shininess() in setting the material properties of shapes. + * + * ( end auto-generated ) + * + * @webref lights_camera:material_properties + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#emissive(float, float, float) + * @see PGraphics#specular(float, float, float) + * @see PGraphics#shininess(float) + */ public void ambient(int rgb) { g.ambient(rgb); } + /** + * @param gray number specifying value between white and black + */ public void ambient(float gray) { g.ambient(gray); } - public void ambient(float x, float y, float z) { - g.ambient(x, y, z); + /** + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + */ + public void ambient(float v1, float v2, float v3) { + g.ambient(v1, v2, v3); } + /** + * ( begin auto-generated from specular.xml ) + * + * Sets the specular color of the materials used for shapes drawn to the + * screen, which sets the color of hightlights. Specular refers to light + * which bounces off a surface in a perferred direction (rather than + * bouncing in all directions like a diffuse light). Used in combination + * with emissive(), ambient(), and shininess() in + * setting the material properties of shapes. + * + * ( end auto-generated ) + * + * @webref lights_camera:material_properties + * @usage web_application + * @param rgb color to set + * @see PGraphics#lightSpecular(float, float, float) + * @see PGraphics#ambient(float, float, float) + * @see PGraphics#emissive(float, float, float) + * @see PGraphics#shininess(float) + */ public void specular(int rgb) { g.specular(rgb); } + /** + * gray number specifying value between white and black + * + * @param gray value between black and white, by default 0 to 255 + */ public void specular(float gray) { g.specular(gray); } - public void specular(float x, float y, float z) { - g.specular(x, y, z); + /** + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + */ + public void specular(float v1, float v2, float v3) { + g.specular(v1, v2, v3); } + /** + * ( begin auto-generated from shininess.xml ) + * + * Sets the amount of gloss in the surface of shapes. Used in combination + * with ambient(), specular(), and emissive() in + * setting the material properties of shapes. + * + * ( end auto-generated ) + * + * @webref lights_camera:material_properties + * @usage web_application + * @param shine degree of shininess + * @see PGraphics#emissive(float, float, float) + * @see PGraphics#ambient(float, float, float) + * @see PGraphics#specular(float, float, float) + */ public void shininess(float shine) { g.shininess(shine); } + /** + * ( begin auto-generated from emissive.xml ) + * + * Sets the emissive color of the material used for drawing shapes drawn to + * the screen. Used in combination with ambient(), + * specular(), and shininess() in setting the material + * properties of shapes. + * + * ( end auto-generated ) + * + * @webref lights_camera:material_properties + * @usage web_application + * @param rgb color to set + * @see PGraphics#ambient(float, float, float) + * @see PGraphics#specular(float, float, float) + * @see PGraphics#shininess(float) + */ public void emissive(int rgb) { g.emissive(rgb); } + /** + * gray number specifying value between white and black + * + * @param gray value between black and white, by default 0 to 255 + */ public void emissive(float gray) { g.emissive(gray); } - public void emissive(float x, float y, float z) { - g.emissive(x, y, z); + /** + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + */ + public void emissive(float v1, float v2, float v3) { + g.emissive(v1, v2, v3); } + /** + * ( begin auto-generated from lights.xml ) + * + * Sets the default ambient light, directional light, falloff, and specular + * values. The defaults are ambientLight(128, 128, 128) and + * directionalLight(128, 128, 128, 0, 0, -1), lightFalloff(1, 0, 0), and + * lightSpecular(0, 0, 0). Lights need to be included in the draw() to + * remain persistent in a looping program. Placing them in the setup() of a + * looping program will cause them to only have an effect the first time + * through the loop. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @see PGraphics#ambientLight(float, float, float, float, float, float) + * @see PGraphics#directionalLight(float, float, float, float, float, float) + * @see PGraphics#pointLight(float, float, float, float, float, float) + * @see PGraphics#spotLight(float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#noLights() + */ public void lights() { g.lights(); } + /** + * ( begin auto-generated from noLights.xml ) + * + * Disable all lighting. Lighting is turned off by default and enabled with + * the lights() function. This function can be used to disable + * lighting so that 2D geometry (which does not require lighting) can be + * drawn after a set of lighted 3D geometry. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @see PGraphics#lights() + */ public void noLights() { g.noLights(); } - public void ambientLight(float red, float green, float blue) { - g.ambientLight(red, green, blue); + /** + * ( begin auto-generated from ambientLight.xml ) + * + * Adds an ambient light. Ambient light doesn't come from a specific + * direction, the rays have light have bounced around so much that objects + * are evenly lit from all sides. Ambient lights are almost always used in + * combination with other types of lights. Lights need to be included in + * the draw() to remain persistent in a looping program. Placing + * them in the setup() of a looping program will cause them to only + * have an effect the first time through the loop. The effect of the + * parameters is determined by the current color mode. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + * @see PGraphics#lights() + * @see PGraphics#directionalLight(float, float, float, float, float, float) + * @see PGraphics#pointLight(float, float, float, float, float, float) + * @see PGraphics#spotLight(float, float, float, float, float, float, float, float, float, float, float) + */ + public void ambientLight(float v1, float v2, float v3) { + g.ambientLight(v1, v2, v3); } - public void ambientLight(float red, float green, float blue, + /** + * @param x x-coordinate of the light + * @param y y-coordinate of the light + * @param z z-coordinate of the light + */ + public void ambientLight(float v1, float v2, float v3, float x, float y, float z) { - g.ambientLight(red, green, blue, x, y, z); + g.ambientLight(v1, v2, v3, x, y, z); } - public void directionalLight(float red, float green, float blue, + /** + * ( begin auto-generated from directionalLight.xml ) + * + * Adds a directional light. Directional light comes from one direction and + * is stronger when hitting a surface squarely and weaker if it hits at a a + * gentle angle. After hitting a surface, a directional lights scatters in + * all directions. Lights need to be included in the draw() to + * remain persistent in a looping program. Placing them in the + * setup() of a looping program will cause them to only have an + * effect the first time through the loop. The affect of the v1, + * v2, and v3 parameters is determined by the current color + * mode. The nx, ny, and nz parameters specify the + * direction the light is facing. For example, setting ny to -1 will + * cause the geometry to be lit from below (the light is facing directly upward). + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + * @param nx direction along the x-axis + * @param ny direction along the y-axis + * @param nz direction along the z-axis + * @see PGraphics#lights() + * @see PGraphics#ambientLight(float, float, float, float, float, float) + * @see PGraphics#pointLight(float, float, float, float, float, float) + * @see PGraphics#spotLight(float, float, float, float, float, float, float, float, float, float, float) + */ + public void directionalLight(float v1, float v2, float v3, float nx, float ny, float nz) { - g.directionalLight(red, green, blue, nx, ny, nz); + g.directionalLight(v1, v2, v3, nx, ny, nz); } - public void pointLight(float red, float green, float blue, + /** + * ( begin auto-generated from pointLight.xml ) + * + * Adds a point light. Lights need to be included in the draw() to + * remain persistent in a looping program. Placing them in the + * setup() of a looping program will cause them to only have an + * effect the first time through the loop. The affect of the v1, + * v2, and v3 parameters is determined by the current color + * mode. The x, y, and z parameters set the position + * of the light. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + * @param x x-coordinate of the light + * @param y y-coordinate of the light + * @param z z-coordinate of the light + * @see PGraphics#lights() + * @see PGraphics#directionalLight(float, float, float, float, float, float) + * @see PGraphics#ambientLight(float, float, float, float, float, float) + * @see PGraphics#spotLight(float, float, float, float, float, float, float, float, float, float, float) + */ + public void pointLight(float v1, float v2, float v3, float x, float y, float z) { - g.pointLight(red, green, blue, x, y, z); + g.pointLight(v1, v2, v3, x, y, z); } - public void spotLight(float red, float green, float blue, + /** + * ( begin auto-generated from spotLight.xml ) + * + * Adds a spot light. Lights need to be included in the draw() to + * remain persistent in a looping program. Placing them in the + * setup() of a looping program will cause them to only have an + * effect the first time through the loop. The affect of the v1, + * v2, and v3 parameters is determined by the current color + * mode. The x, y, and z parameters specify the + * position of the light and nx, ny, nz specify the + * direction or light. The angle parameter affects angle of the + * spotlight cone. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + * @param x x-coordinate of the light + * @param y y-coordinate of the light + * @param z z-coordinate of the light + * @param nx direction along the x axis + * @param ny direction along the y axis + * @param nz direction along the z axis + * @param angle angle of the spotlight cone + * @param concentration exponent determining the center bias of the cone + * @see PGraphics#lights() + * @see PGraphics#directionalLight(float, float, float, float, float, float) + * @see PGraphics#pointLight(float, float, float, float, float, float) + * @see PGraphics#ambientLight(float, float, float, float, float, float) + */ + public void spotLight(float v1, float v2, float v3, float x, float y, float z, float nx, float ny, float nz, float angle, float concentration) { - g.spotLight(red, green, blue, x, y, z, nx, ny, nz, angle, concentration); + g.spotLight(v1, v2, v3, x, y, z, nx, ny, nz, angle, concentration); } + /** + * ( begin auto-generated from lightFalloff.xml ) + * + * Sets the falloff rates for point lights, spot lights, and ambient + * lights. The parameters are used to determine the falloff with the + * following equation:

    d = distance from light position to + * vertex position
    falloff = 1 / (CONSTANT + d * LINEAR + (d*d) * + * QUADRATIC)

    Like fill(), it affects only the elements + * which are created after it in the code. The default value if + * LightFalloff(1.0, 0.0, 0.0). Thinking about an ambient light with + * a falloff can be tricky. It is used, for example, if you wanted a region + * of your scene to be lit ambiently one color and another region to be lit + * ambiently by another color, you would use an ambient light with location + * and falloff. You can think of it as a point light that doesn't care + * which direction a surface is facing. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @param constant constant value or determining falloff + * @param linear linear value for determining falloff + * @param quadratic quadratic value for determining falloff + * @see PGraphics#lights() + * @see PGraphics#ambientLight(float, float, float, float, float, float) + * @see PGraphics#pointLight(float, float, float, float, float, float) + * @see PGraphics#spotLight(float, float, float, float, float, float, float, float, float, float, float) + * @see PGraphics#lightSpecular(float, float, float) + */ public void lightFalloff(float constant, float linear, float quadratic) { g.lightFalloff(constant, linear, quadratic); } - public void lightSpecular(float x, float y, float z) { - g.lightSpecular(x, y, z); + /** + * ( begin auto-generated from lightSpecular.xml ) + * + * Sets the specular color for lights. Like fill(), it affects only + * the elements which are created after it in the code. Specular refers to + * light which bounces off a surface in a perferred direction (rather than + * bouncing in all directions like a diffuse light) and is used for + * creating highlights. The specular quality of a light interacts with the + * specular material qualities set through the specular() and + * shininess() functions. + * + * ( end auto-generated ) + * + * @webref lights_camera:lights + * @usage web_application + * @param v1 red or hue value (depending on current color mode) + * @param v2 green or saturation value (depending on current color mode) + * @param v3 blue or brightness value (depending on current color mode) + * @see PGraphics#specular(float, float, float) + * @see PGraphics#lights() + * @see PGraphics#ambientLight(float, float, float, float, float, float) + * @see PGraphics#pointLight(float, float, float, float, float, float) + * @see PGraphics#spotLight(float, float, float, float, float, float, float, float, float, float, float) + */ + public void lightSpecular(float v1, float v2, float v3) { + g.lightSpecular(v1, v2, v3); } /** - * Set the background to a gray or ARGB color. - *

    - * For the main drawing surface, the alpha value will be ignored. However, - * alpha can be used on PGraphics objects from createGraphics(). This is - * the only way to set all the pixels partially transparent, for instance. - *

    - * Note that background() should be called before any transformations occur, - * because some implementations may require the current transformation matrix - * to be identity before drawing. + * ( begin auto-generated from background.xml ) + * + * The background() function sets the color used for the background + * of the Processing window. The default background is light gray. In the + * draw() function, the background color is used to clear the + * display window at the beginning of each frame. + *

    + * An image can also be used as the background for a sketch, however its + * width and height must be the same size as the sketch window. To resize + * an image 'b' to the size of the sketch window, use b.resize(width, height). + *

    + * Images used as background will ignore the current tint() setting. + *

    + * It is not possible to use transparency (alpha) in background colors with + * the main drawing surface, however they will work properly with createGraphics(). + * + * ( end auto-generated ) + * + *

    Advanced

    + *

    Clear the background with a color that includes an alpha value. This can + * only be used with objects created by createGraphics(), because the main + * drawing surface cannot be set transparent.

    + *

    It might be tempting to use this function to partially clear the screen + * on each frame, however that's not how this function works. When calling + * background(), the pixels will be replaced with pixels that have that level + * of transparency. To do a semi-transparent overlay, use fill() with alpha + * and draw a rectangle.

    + * + * @webref color:setting + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#stroke(float) + * @see PGraphics#fill(float) + * @see PGraphics#tint(float) + * @see PGraphics#colorMode(int) */ public void background(int rgb) { g.background(rgb); @@ -9341,7 +11976,7 @@ public void background(int rgb) { /** - * See notes about alpha in background(x, y, z, a). + * @param alpha opacity of the background */ public void background(int rgb, float alpha) { g.background(rgb, alpha); @@ -9349,47 +11984,36 @@ public void background(int rgb, float alpha) { /** - * Set the background to a grayscale value, based on the - * current colorMode. + * @param gray specifies a value between white and black */ public void background(float gray) { g.background(gray); } - /** - * See notes about alpha in background(x, y, z, a). - */ public void background(float gray, float alpha) { g.background(gray, alpha); } /** - * Set the background to an r, g, b or h, s, b value, - * based on the current colorMode. + * @param v1 red or hue value (depending on the current color mode) + * @param v2 green or saturation value (depending on the current color mode) + * @param v3 blue or brightness value (depending on the current color mode) */ - public void background(float x, float y, float z) { - g.background(x, y, z); + public void background(float v1, float v2, float v3) { + g.background(v1, v2, v3); } - /** - * Clear the background with a color that includes an alpha value. This can - * only be used with objects created by createGraphics(), because the main - * drawing surface cannot be set transparent. - *

    - * It might be tempting to use this function to partially clear the screen - * on each frame, however that's not how this function works. When calling - * background(), the pixels will be replaced with pixels that have that level - * of transparency. To do a semi-transparent overlay, use fill() with alpha - * and draw a rectangle. - */ - public void background(float x, float y, float z, float a) { - g.background(x, y, z, a); + public void background(float v1, float v2, float v3, float alpha) { + g.background(v1, v2, v3, alpha); } + /** + * @webref color:setting + */ public void clear() { g.clear(); } @@ -9398,94 +12022,255 @@ public void clear() { /** * Takes an RGB or ARGB image and sets it as the background. * The width and height of the image must be the same size as the sketch. - * Use image.resize(width, height) to make short work of such a task. - *

    + * Use image.resize(width, height) to make short work of such a task.
    + *
    * Note that even if the image is set as RGB, the high 8 bits of each pixel - * should be set opaque (0xFF000000), because the image data will be copied + * should be set opaque (0xFF000000) because the image data will be copied * directly to the screen, and non-opaque background images may have strange - * behavior. Using image.filter(OPAQUE) will handle this easily. - *

    + * behavior. Use image.filter(OPAQUE) to handle this easily.
    + *
    * When using 3D, this will also clear the zbuffer (if it exists). + * + * @param image PImage to set as background (must be same size as the sketch window) */ public void background(PImage image) { g.background(image); } + /** + * ( begin auto-generated from colorMode.xml ) + * + * Changes the way Processing interprets color data. By default, the + * parameters for fill(), stroke(), background(), and + * color() are defined by values between 0 and 255 using the RGB + * color model. The colorMode() function is used to change the + * numerical range used for specifying colors and to switch color systems. + * For example, calling colorMode(RGB, 1.0) will specify that values + * are specified between 0 and 1. The limits for defining colors are + * altered by setting the parameters range1, range2, range3, and range 4. + * + * ( end auto-generated ) + * + * @webref color:setting + * @usage web_application + * @param mode Either RGB or HSB, corresponding to Red/Green/Blue and Hue/Saturation/Brightness + * @see PGraphics#background(float) + * @see PGraphics#fill(float) + * @see PGraphics#stroke(float) + */ public void colorMode(int mode) { g.colorMode(mode); } + /** + * @param max range for all color elements + */ public void colorMode(int mode, float max) { g.colorMode(mode, max); } /** - * Set the colorMode and the maximum values for (r, g, b) - * or (h, s, b). - *

    - * Note that this doesn't set the maximum for the alpha value, - * which might be confusing if for instance you switched to - *

    colorMode(HSB, 360, 100, 100);
    - * because the alpha values were still between 0 and 255. + * @param max1 range for the red or hue depending on the current color mode + * @param max2 range for the green or saturation depending on the current color mode + * @param max3 range for the blue or brightness depending on the current color mode */ - public void colorMode(int mode, float maxX, float maxY, float maxZ) { - g.colorMode(mode, maxX, maxY, maxZ); + public void colorMode(int mode, float max1, float max2, float max3) { + g.colorMode(mode, max1, max2, max3); } + /** + * @param maxA range for the alpha + */ public void colorMode(int mode, - float maxX, float maxY, float maxZ, float maxA) { - g.colorMode(mode, maxX, maxY, maxZ, maxA); - } - - - public final float alpha(int what) { - return g.alpha(what); + float max1, float max2, float max3, float maxA) { + g.colorMode(mode, max1, max2, max3, maxA); } - public final float red(int what) { - return g.red(what); + /** + * ( begin auto-generated from alpha.xml ) + * + * Extracts the alpha value from a color. + * + * ( end auto-generated ) + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#red(int) + * @see PGraphics#green(int) + * @see PGraphics#blue(int) + * @see PGraphics#hue(int) + * @see PGraphics#saturation(int) + * @see PGraphics#brightness(int) + */ + public final float alpha(int rgb) { + return g.alpha(rgb); } - public final float green(int what) { - return g.green(what); + /** + * ( begin auto-generated from red.xml ) + * + * Extracts the red value from a color, scaled to match current + * colorMode(). This value is always returned as a float so be + * careful not to assign it to an int value.

    The red() function + * is easy to use and undestand, but is slower than another technique. To + * achieve the same results when working in colorMode(RGB, 255), but + * with greater speed, use the >> (right shift) operator with a bit + * mask. For example, the following two lines of code are equivalent:
    float r1 = red(myColor);
    float r2 = myColor >> 16 + * & 0xFF;
    + * + * ( end auto-generated ) + * + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#green(int) + * @see PGraphics#blue(int) + * @see PGraphics#alpha(int) + * @see PGraphics#hue(int) + * @see PGraphics#saturation(int) + * @see PGraphics#brightness(int) + * @see_external rightshift + */ + public final float red(int rgb) { + return g.red(rgb); } - public final float blue(int what) { - return g.blue(what); + /** + * ( begin auto-generated from green.xml ) + * + * Extracts the green value from a color, scaled to match current + * colorMode(). This value is always returned as a float so be + * careful not to assign it to an int value.

    The green() + * function is easy to use and undestand, but is slower than another + * technique. To achieve the same results when working in colorMode(RGB, + * 255), but with greater speed, use the >> (right shift) + * operator with a bit mask. For example, the following two lines of code + * are equivalent:
    float r1 = green(myColor);
    float r2 = + * myColor >> 8 & 0xFF;
    + * + * ( end auto-generated ) + * + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#red(int) + * @see PGraphics#blue(int) + * @see PGraphics#alpha(int) + * @see PGraphics#hue(int) + * @see PGraphics#saturation(int) + * @see PGraphics#brightness(int) + * @see_external rightshift + */ + public final float green(int rgb) { + return g.green(rgb); } - public final float hue(int what) { - return g.hue(what); + /** + * ( begin auto-generated from blue.xml ) + * + * Extracts the blue value from a color, scaled to match current + * colorMode(). This value is always returned as a float so be + * careful not to assign it to an int value.

    The blue() + * function is easy to use and undestand, but is slower than another + * technique. To achieve the same results when working in colorMode(RGB, + * 255), but with greater speed, use a bit mask to remove the other + * color components. For example, the following two lines of code are + * equivalent:
    float r1 = blue(myColor);
    float r2 = myColor + * & 0xFF;
    + * + * ( end auto-generated ) + * + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#red(int) + * @see PGraphics#green(int) + * @see PGraphics#alpha(int) + * @see PGraphics#hue(int) + * @see PGraphics#saturation(int) + * @see PGraphics#brightness(int) + * @see_external rightshift + */ + public final float blue(int rgb) { + return g.blue(rgb); } - public final float saturation(int what) { - return g.saturation(what); + /** + * ( begin auto-generated from hue.xml ) + * + * Extracts the hue value from a color. + * + * ( end auto-generated ) + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#red(int) + * @see PGraphics#green(int) + * @see PGraphics#blue(int) + * @see PGraphics#alpha(int) + * @see PGraphics#saturation(int) + * @see PGraphics#brightness(int) + */ + public final float hue(int rgb) { + return g.hue(rgb); } - public final float brightness(int what) { - return g.brightness(what); + /** + * ( begin auto-generated from saturation.xml ) + * + * Extracts the saturation value from a color. + * + * ( end auto-generated ) + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#red(int) + * @see PGraphics#green(int) + * @see PGraphics#blue(int) + * @see PGraphics#alpha(int) + * @see PGraphics#hue(int) + * @see PGraphics#brightness(int) + */ + public final float saturation(int rgb) { + return g.saturation(rgb); } /** - * Interpolate between two colors, using the current color mode. + * ( begin auto-generated from brightness.xml ) + * + * Extracts the brightness value from a color. + * + * ( end auto-generated ) + * + * @webref color:creating_reading + * @usage web_application + * @param rgb any value of the color datatype + * @see PGraphics#red(int) + * @see PGraphics#green(int) + * @see PGraphics#blue(int) + * @see PGraphics#alpha(int) + * @see PGraphics#hue(int) + * @see PGraphics#saturation(int) */ - public int lerpColor(int c1, int c2, float amt) { - return g.lerpColor(c1, c2, amt); + public final float brightness(int rgb) { + return g.brightness(rgb); } /** + * @nowebref * Interpolate between two colors. Like lerp(), but for the * individual color components of a color supplied as an int value. */ @@ -9542,35 +12327,30 @@ static public void showMissingWarning(String method) { /** - * Return true if this renderer should be drawn to the screen. Defaults to - * returning true, since nearly all renderers are on-screen beasts. But can - * be overridden for subclasses like PDF so that a window doesn't open up. - *

    - * A better name? showFrame, displayable, isVisible, visible, shouldDisplay, - * what to call this? - */ - public boolean displayable() { - return g.displayable(); - } - - - /** - * Return true if this renderer does rendering through OpenGL. Defaults to false. - */ - public boolean isGL() { - return g.isGL(); - } - - - /** - * Returns the native Bitmap object for this PImage. - */ - public Object getNative() { - return g.getNative(); - } - - - /** + * ( begin auto-generated from PImage_get.xml ) + * + * Reads the color of any pixel or grabs a section of an image. If no + * parameters are specified, the entire image is returned. Use the x + * and y parameters to get the value of one pixel. Get a section of + * the display window by specifying an additional width and + * height parameter. When getting an image, the x and + * y parameters define the coordinates for the upper-left corner of + * the image, regardless of the current imageMode().
    + *
    + * If the pixel requested is outside of the image window, black is + * returned. The numbers returned are scaled according to the current color + * ranges, but only RGB values are returned by this function. For example, + * even though you may have drawn a shape with colorMode(HSB), the + * numbers returned will be in RGB format.
    + *
    + * Getting the color of a single pixel with get(x, y) is easy, but + * not as fast as grabbing the data directly from pixels[]. The + * equivalent statement to get(x, y) using pixels[] is + * pixels[y*width+x]. See the reference for pixels[] for more information. + * + * ( end auto-generated ) + * + *

    Advanced

    * Returns an ARGB "color" type (a packed 32 bit int with the color. * If the coordinate is outside the image, zero is returned * (black, but completely transparent). @@ -9587,16 +12367,21 @@ public Object getNative() { * is inside the bounds, and then has to check to see what image * type it is. If you want things to be more efficient, access the * pixels[] array directly. + * + * @webref image:pixels + * @brief Reads the color of any pixel or grabs a rectangle of pixels + * @usage web_application + * @param x x-coordinate of the pixel + * @param y y-coordinate of the pixel + * @see PApplet#set(int, int, int) + * @see PApplet#pixels + * @see PApplet#copy(PImage, int, int, int, int, int, int, int, int) */ public int get(int x, int y) { return g.get(x, y); } - /** - * Grab a subsection of a PImage, and copy it into a fresh PImage. - * As of release 0149, no longer honors imageMode() for the coordinates. - */ /** * @param w width of pixel rectangle to get * @param h height of pixel rectangle to get @@ -9608,14 +12393,48 @@ public PImage get(int x, int y, int w, int h) { /** * Returns a copy of this PImage. Equivalent to get(0, 0, width, height). + * Deprecated, just use copy() instead. */ public PImage get() { return g.get(); } + public PImage copy() { + return g.copy(); + } + + /** - * Set a single pixel to the specified color. + * ( begin auto-generated from PImage_set.xml ) + * + * Changes the color of any pixel or writes an image directly into the + * display window.
    + *
    + * The x and y parameters specify the pixel to change and the + * color parameter specifies the color value. The color parameter is + * affected by the current color mode (the default is RGB values from 0 to + * 255). When setting an image, the x and y parameters define + * the coordinates for the upper-left corner of the image, regardless of + * the current imageMode(). + *

    + * Setting the color of a single pixel with set(x, y) is easy, but + * not as fast as putting the data directly into pixels[]. The + * equivalent statement to set(x, y, #000000) using pixels[] + * is pixels[y*width+x] = #000000. See the reference for + * pixels[] for more information. + * + * ( end auto-generated ) + * + * @webref image:pixels + * @brief writes a color to any pixel or writes an image into another + * @usage web_application + * @param x x-coordinate of the pixel + * @param y y-coordinate of the pixel + * @param c any value of the color datatype + * @see PImage#get(int, int, int, int) + * @see PImage#pixels + * @see PImage#copy(PImage, int, int, int, int, int, int, int, int) */ public void set(int x, int y, int c) { g.set(x, y, c); @@ -9623,9 +12442,12 @@ public void set(int x, int y, int c) { /** + *

    Advanced

    * Efficient method of drawing an image's pixels directly to this surface. * No variations are employed, meaning that any scale, tint, or imageMode * settings will be ignored. + * + * @param img image to copy into the original image */ public void set(int x, int y, PImage img) { g.set(x, y, img); @@ -9633,6 +12455,23 @@ public void set(int x, int y, PImage img) { /** + * ( begin auto-generated from PImage_mask.xml ) + * + * Masks part of an image from displaying by loading another image and + * using it as an alpha channel. This mask image should only contain + * grayscale data, but only the blue color channel is used. The mask image + * needs to be the same size as the image to which it is applied.
    + *
    + * In addition to using a mask image, an integer array containing the alpha + * channel data can be specified directly. This method is useful for + * creating dynamically generated alpha masks. This array must be of the + * same length as the target image's pixels array and should contain only + * grayscale data of values between 0-255. + * + * ( end auto-generated ) + * + *

    Advanced

    + * * Set alpha channel for an image. Black colors in the source * image will make the destination image completely transparent, * and white will make things fully opaque. Gray values will @@ -9642,23 +12481,54 @@ public void set(int x, int y, PImage img) { * used as the alpha color. For a fully grayscale image, this * is correct, but for a color image it's not 100% accurate. * For a more accurate conversion, first use filter(GRAY) - * which will make the image into a "correct" grayscake by + * which will make the image into a "correct" grayscale by * performing a proper luminance-based conversion. + * + * @webref pimage:method + * @usage web_application + * @param img image to use as the mask + * @brief Masks part of an image with another image as an alpha channel */ - public void mask(int alpha[]) { - g.mask(alpha); + public void mask(PImage img) { + g.mask(img); } - /** - * Set alpha channel for an image using another image as the source. - */ - public void mask(PImage alpha) { - g.mask(alpha); + public void filter(int kind) { + g.filter(kind); } /** + * ( begin auto-generated from PImage_filter.xml ) + * + * Filters an image as defined by one of the following modes:

    THRESHOLD - converts the image to black and white pixels depending if + * they are above or below the threshold defined by the level parameter. + * The level must be between 0.0 (black) and 1.0(white). If no level is + * specified, 0.5 is used.
    + *
    + * GRAY - converts any colors in the image to grayscale equivalents
    + *
    + * INVERT - sets each pixel to its inverse value
    + *
    + * POSTERIZE - limits each channel of the image to the number of colors + * specified as the level parameter
    + *
    + * BLUR - executes a Guassian blur with the level parameter specifying the + * extent of the blurring. If no level parameter is used, the blur is + * equivalent to Guassian blur of radius 1
    + *
    + * OPAQUE - sets the alpha channel to entirely opaque
    + *
    + * ERODE - reduces the light areas with the amount defined by the level + * parameter
    + *
    + * DILATE - increases the light areas with the amount defined by the level parameter + * + * ( end auto-generated ) + * + *

    Advanced

    * Method to apply a variety of basic filters to this image. *

    *

      @@ -9675,27 +12545,12 @@ public void mask(PImage alpha) { *

      * Gaussian blur code contributed by * Mario Klingemann - */ - public void filter(int kind) { - g.filter(kind); - } - - - /** - * Method to apply a variety of basic filters to this image. - * These filters all take a parameter. - *

      - *

        - *
      • filter(BLUR, int radius) performs a gaussian blur of the - * specified radius. - *
      • filter(POSTERIZE, int levels) will posterize the image to - * between 2 and 255 levels. - *
      • filter(THRESHOLD, float center) allows you to set the - * center point for the threshold. It takes a value from 0 to 1.0. - *
      - * Gaussian blur code contributed by - * Mario Klingemann - * and later updated by toxi for better speed. + * + * @webref image:pixels + * @brief Converts the image to grayscale or black and white + * @usage web_application + * @param kind Either THRESHOLD, GRAY, OPAQUE, INVERT, POSTERIZE, BLUR, ERODE, or DILATE + * @param param unique for each, see above */ public void filter(int kind, float param) { g.filter(kind, param); @@ -9703,8 +12558,31 @@ public void filter(int kind, float param) { /** - * Copy things from one area of this image - * to another area in the same image. + * ( begin auto-generated from PImage_copy.xml ) + * + * Copies a region of pixels from one image into another. If the source and + * destination regions aren't the same size, it will automatically resize + * source pixels to fit the specified target region. No alpha information + * is used in the process, however if the source image has an alpha channel + * set, it will be copied as well. + *

      + * As of release 0149, this function ignores imageMode(). + * + * ( end auto-generated ) + * + * @webref image:pixels + * @brief Copies the entire image + * @usage web_application + * @param sx X coordinate of the source's upper left corner + * @param sy Y coordinate of the source's upper left corner + * @param sw source image width + * @param sh source image height + * @param dx X coordinate of the destination's upper left corner + * @param dy Y coordinate of the destination's upper left corner + * @param dw destination image width + * @param dh destination image height + * @see PGraphics#alpha(int) + * @see PImage#blend(PImage, int, int, int, int, int, int, int, int, int) */ public void copy(int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) { @@ -9713,7 +12591,7 @@ public void copy(int sx, int sy, int sw, int sh, /** - * Copies area of one image into another PImage object. + * @param src an image variable referring to the source image. */ public void copy(PImage src, int sx, int sy, int sw, int sh, @@ -9722,10 +12600,6 @@ public void copy(PImage src, } - /** - * Blends one area of this image to another area. - * @see processing.core.PImage#blendColor(int,int,int) - */ public void blend(int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, int mode) { g.blend(sx, sy, sw, sh, dx, dy, dw, dh, mode); @@ -9733,8 +12607,72 @@ public void blend(int sx, int sy, int sw, int sh, /** - * Copies area of one image into another PImage object. - * @see processing.core.PImage#blendColor(int,int,int) + * ( begin auto-generated from PImage_blend.xml ) + * + * Blends a region of pixels into the image specified by the img + * parameter. These copies utilize full alpha channel support and a choice + * of the following modes to blend the colors of source pixels (A) with the + * ones of pixels in the destination image (B):
      + *
      + * BLEND - linear interpolation of colours: C = A*factor + B
      + *
      + * ADD - additive blending with white clip: C = min(A*factor + B, 255)
      + *
      + * SUBTRACT - subtractive blending with black clip: C = max(B - A*factor, + * 0)
      + *
      + * DARKEST - only the darkest colour succeeds: C = min(A*factor, B)
      + *
      + * LIGHTEST - only the lightest colour succeeds: C = max(A*factor, B)
      + *
      + * DIFFERENCE - subtract colors from underlying image.
      + *
      + * EXCLUSION - similar to DIFFERENCE, but less extreme.
      + *
      + * MULTIPLY - Multiply the colors, result will always be darker.
      + *
      + * SCREEN - Opposite multiply, uses inverse values of the colors.
      + *
      + * OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values, + * and screens light values.
      + *
      + * HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower.
      + *
      + * SOFT_LIGHT - Mix of DARKEST and LIGHTEST. + * Works like OVERLAY, but not as harsh.
      + *
      + * DODGE - Lightens light tones and increases contrast, ignores darks. + * Called "Color Dodge" in Illustrator and Photoshop.
      + *
      + * BURN - Darker areas are applied, increasing contrast, ignores lights. + * Called "Color Burn" in Illustrator and Photoshop.
      + *
      + * All modes use the alpha information (highest byte) of source image + * pixels as the blending factor. If the source and destination regions are + * different sizes, the image will be automatically resized to match the + * destination size. If the srcImg parameter is not used, the + * display window is used as the source image.
      + *
      + * As of release 0149, this function ignores imageMode(). + * + * ( end auto-generated ) + * + * @webref image:pixels + * @brief Copies a pixel or rectangle of pixels using different blending modes + * @param src an image variable referring to the source image + * @param sx X coordinate of the source's upper left corner + * @param sy Y coordinate of the source's upper left corner + * @param sw source image width + * @param sh source image height + * @param dx X coordinate of the destinations's upper left corner + * @param dy Y coordinate of the destinations's upper left corner + * @param dw destination image width + * @param dh destination image height + * @param mode Either BLEND, ADD, SUBTRACT, LIGHTEST, DARKEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, OVERLAY, HARD_LIGHT, SOFT_LIGHT, DODGE, BURN + * + * @see PApplet#alpha(int) + * @see PImage#copy(PImage, int, int, int, int, int, int, int, int) + * @see PImage#blendColor(int,int,int) */ public void blend(PImage src, int sx, int sy, int sw, int sh, diff --git a/core/src/processing/core/PConstants.java b/libs/processing-core/src/main/java/processing/core/PConstants.java similarity index 92% rename from core/src/processing/core/PConstants.java rename to libs/processing-core/src/main/java/processing/core/PConstants.java index 582509acc..14753a133 100644 --- a/core/src/processing/core/PConstants.java +++ b/libs/processing-core/src/main/java/processing/core/PConstants.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2004-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or @@ -45,8 +46,14 @@ public interface PConstants { // built-in rendering options static final String JAVA2D = "processing.core.PGraphicsAndroid2D"; static final String P2D = "processing.opengl.PGraphics2D"; + static final String P2DX = "processing.opengl.PGraphics2DX"; static final String P3D = "processing.opengl.PGraphics3D"; static final String OPENGL = P3D; + static final String STEREO = "processing.vr.VRGraphicsStereo"; + static final String MONO = "processing.vr.VRGraphicsMono"; + static final String VR = STEREO; + static final String AR = "processing.ar.ARGraphics"; + static final String ARCORE = AR; // The PDF and DXF renderers are not available for Android. @@ -346,12 +353,12 @@ public interface PConstants { // for 0125, these were changed to 'char' values, because they // can be upgraded to ints automatically by Java, but having them // as ints prevented split(blah, TAB) from working - static final char BACKSPACE = 8; - static final char TAB = 9; - static final char ENTER = 10; - static final char RETURN = 13; - static final char ESC = 27; - static final char DELETE = 127; + static final char BACKSPACE = KeyEvent.KEYCODE_DEL; + static final char TAB = KeyEvent.KEYCODE_TAB; + static final char ENTER = KeyEvent.KEYCODE_ENTER; + static final char RETURN = KeyEvent.KEYCODE_ENTER; + static final char ESC = KeyEvent.KEYCODE_ESCAPE; + static final char DELETE = KeyEvent.KEYCODE_DEL; // i.e. if ((key == CODED) && (keyCode == UP)) static final int CODED = 0xffff; @@ -373,8 +380,7 @@ public interface PConstants { // key will be CODED and keyCode will be this value // static final int ALT = KeyEvent.VK_ALT; // static final int CONTROL = KeyEvent.VK_CONTROL; -// static final int SHIFT = KeyEvent.VK_SHIFT; - + static final int SHIFT = KeyEvent.KEYCODE_SHIFT_LEFT; // cursor types @@ -395,7 +401,9 @@ public interface PConstants { // hints - hint values are positive for the alternate version, // negative of the same value returns to the normal/default state + @Deprecated static final int ENABLE_NATIVE_FONTS = 1; + @Deprecated static final int DISABLE_NATIVE_FONTS = -1; static final int DISABLE_DEPTH_TEST = 2; @@ -422,7 +430,16 @@ public interface PConstants { static final int ENABLE_STROKE_PURE = 9; static final int DISABLE_STROKE_PURE = -9; - static final int HINT_COUNT = 10; + static final int ENABLE_BUFFER_READING = 10; + static final int DISABLE_BUFFER_READING = -10; + + static final int DISABLE_KEY_REPEAT = 11; + static final int ENABLE_KEY_REPEAT = -11; + + static final int DISABLE_ASYNC_SAVEFRAME = 12; + static final int ENABLE_ASYNC_SAVEFRAME = -12; + + static final int HINT_COUNT = 13; // error messages diff --git a/core/src/processing/core/PFont.java b/libs/processing-core/src/main/java/processing/core/PFont.java similarity index 98% rename from core/src/processing/core/PFont.java rename to libs/processing-core/src/main/java/processing/core/PFont.java index 7e71bed25..48a01b3cc 100644 --- a/core/src/processing/core/PFont.java +++ b/libs/processing-core/src/main/java/processing/core/PFont.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2004-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or @@ -417,6 +418,19 @@ public int getSize() { } + /** + * Returns the size that will be used when textFont(font) is called. + */ + public int getDefaultSize() { + return size; + } + + + public boolean isSmooth() { + return smooth; + } + + public void setSubsetting() { subsetting = true; } diff --git a/core/src/processing/core/PGraphics.java b/libs/processing-core/src/main/java/processing/core/PGraphics.java similarity index 86% rename from core/src/processing/core/PGraphics.java rename to libs/processing-core/src/main/java/processing/core/PGraphics.java index a7e922343..dc1657cb2 100644 --- a/core/src/processing/core/PGraphics.java +++ b/libs/processing-core/src/main/java/processing/core/PGraphics.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2004-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or @@ -25,12 +26,18 @@ import java.util.HashMap; import java.util.WeakHashMap; - +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +import processing.android.AppComponent; import processing.opengl.PGL; import processing.opengl.PShader; - -import android.graphics.Bitmap; import android.graphics.Color; +import android.view.SurfaceHolder; /** @@ -118,26 +125,26 @@ public class PGraphics extends PImage implements PConstants { // width and height are already inherited from PImage - /// width minus one (useful for many calculations) - protected int width1; - - /// height minus one (useful for many calculations) - protected int height1; +// /// width minus one (useful for many calculations) +// protected int width1; +// +// /// height minus one (useful for many calculations) +// protected int height1; /// width * height (useful for many calculations) public int pixelCount; /// true if smoothing is enabled (read-only) - public boolean smooth = false; - - /// the anti-aliasing level for renderers that support it - protected int quality; + public int smooth; // ........................................................ /// true if defaults() has been called a first time protected boolean settingsInited; + /// true if settings should be re-applied on next beginDraw() + protected boolean reapplySettings; + /// set to a PGraphics object being used inside a beginRaw/endRaw() block protected PGraphics raw; @@ -152,7 +159,7 @@ public class PGraphics extends PImage implements PConstants { * created any other way than size(). When this is set, the listeners * are also added to the sketch. */ - protected boolean primarySurface; + protected boolean primaryGraphics; // ........................................................ @@ -473,16 +480,6 @@ public class PGraphics extends PImage implements PConstants { // ........................................................ - /** - * Java AWT Image object associated with this renderer. For P2D and P3D, - * this will be associated with their MemoryImageSource. For PGraphicsJava2D, - * it will be the offscreen drawing buffer. - */ - //public Image image; - public Bitmap bitmap; - - // ........................................................ - // internal color for setting/calculating protected float calcR, calcG, calcB, calcA; protected int calcRi, calcGi, calcBi, calcAi; @@ -643,6 +640,22 @@ public class PGraphics extends PImage implements PConstants { /// Number of V steps (aka "phi") along latitudinally top-to-bottom spanning pi public int sphereDetailV = 0; + // ........................................................ + + // Variables used to save the surface contents before the activity is taken to the background. + protected String restoreFilename; + protected int restoreWidth, restoreHeight; + protected int restoreCount; + protected boolean restartedLoopingAfterResume = false; + protected boolean restoredSurface = true; + + // This auxiliary variable is used to implement a little hack that fixes + // https://github.com/processing/processing-android/issues/147 + // on older devices where the last frame cannot be maintained after ending + // the rendering in GL. The trick consists in running one more frame after the + // noLoop() call, which ensures that the FBO layer is properly initialized + // and drawn with the contents of the previous frame. + protected boolean requestedNoLoop = false; ////////////////////////////////////////////////////////////// @@ -670,12 +683,12 @@ public void setParent(PApplet parent) { // ignore * else that goes along with that. */ public void setPrimary(boolean primary) { // ignore - this.primarySurface = primary; + this.primaryGraphics = primary; // base images must be opaque (for performance and general // headache reasons.. argh, a semi-transparent opengl surface?) // use createGraphics() if you want a transparent surface. - if (primarySurface) { + if (primaryGraphics) { format = RGB; } } @@ -690,6 +703,14 @@ public void setFrameRate(float framerate) { // ignore } + public void surfaceChanged() { // ignore + } + + + public void reset() { // ignore + } + + /** * The final step in setting up a renderer, set its size of this renderer. * This was formerly handled by the constructor, but instead it's been broken @@ -704,19 +725,12 @@ public void setFrameRate(float framerate) { // ignore public void setSize(int w, int h) { // ignore width = w; height = h; - width1 = width - 1; - height1 = height - 1; - - allocate(); - reapplySettings(); - } + pixelWidth = width * pixelDensity; + pixelHeight = height * pixelDensity; - /** - * Allocate memory for this renderer. Generally will need to be implemented - * for all renderers. - */ - protected void allocate() { } + reapplySettings = true; + } /** @@ -727,9 +741,14 @@ protected void allocate() { } * endRaw(), in order to shut things off. */ public void dispose() { // ignore + parent = null; } + public PSurface createSurface(AppComponent component, SurfaceHolder holder, boolean reset) { // ignore + return null; + } + ////////////////////////////////////////////////////////////// @@ -741,10 +760,10 @@ public void dispose() { // ignore * image data, for instance a BufferedImage with tint() settings applied for * PGraphicsJava2D, or resized image data and OpenGL texture indices for * PGraphicsOpenGL. - * @param renderer The PGraphics renderer associated to the image + * @param image The image to be stored * @param storage The metadata required by the renderer */ - public void setCache(PImage image, Object storage) { + public void setCache(PImage image, Object storage) { // ignore cacheMap.put(image, storage); } @@ -754,24 +773,22 @@ public void setCache(PImage image, Object storage) { * will cache data in different formats, it's necessary to store cache data * keyed by the renderer object. Otherwise, attempting to draw the same * image to both a PGraphicsJava2D and a PGraphicsOpenGL will cause errors. - * @param renderer The PGraphics renderer associated to the image * @return metadata stored for the specified renderer */ - public Object getCache(PImage image) { + public Object getCache(PImage image) { // ignore return cacheMap.get(image); } /** * Remove information associated with this renderer from the cache, if any. - * @param renderer The PGraphics renderer whose cache data should be removed + * @param image The image whose cache data should be removed */ - public void removeCache(PImage image) { + public void removeCache(PImage image) { // ignore cacheMap.remove(image); } - ////////////////////////////////////////////////////////////// // FRAME @@ -788,16 +805,16 @@ public void requestFocus() { // ignore /** * Some renderers have requirements re: when they are ready to draw. */ - public boolean canDraw() { // ignore - return true; - } +// public boolean canDraw() { // ignore +// return true; +// } /** * Try to draw, or put a draw request on the queue. */ - public void requestDraw() { // ignore - } +// public void requestDraw() { // ignore +// } /** @@ -838,6 +855,7 @@ public void endPGL() { protected void checkSettings() { if (!settingsInited) defaultSettings(); + if (reapplySettings) reapplySettings(); } @@ -851,8 +869,6 @@ protected void checkSettings() { * This is currently called by checkSettings(), during beginDraw(). */ protected void defaultSettings() { // ignore - smooth(); // 2.0a5 - colorMode(RGB, 255); fill(255); stroke(0); @@ -886,7 +902,7 @@ protected void defaultSettings() { // ignore // a gray background (when just a transparent surface or an empty pdf // is what's desired). // this background() call is for the Java 2D and OpenGL renderers. - if (primarySurface) { + if (primaryGraphics) { //System.out.println("main drawing surface bg " + getClass().getName()); background(backgroundColor); } @@ -895,7 +911,7 @@ protected void defaultSettings() { // ignore settingsInited = true; // defaultSettings() overlaps reapplySettings(), don't do both - //reapplySettings = false; + reapplySettings = false; } @@ -909,7 +925,7 @@ protected void defaultSettings() { // ignore * size(), which is safely called from inside beginDraw(). And it cannot be * called before defaultSettings(), so we should be safe. */ - protected void reapplySettings() { + protected void reapplySettings() { // ignore // System.out.println("attempting reapplySettings()"); if (!settingsInited) return; // if this is the initial setup, no need to reapply @@ -945,12 +961,12 @@ protected void reapplySettings() { } else { noTint(); } - if (smooth) { - smooth(); - } else { - // Don't bother setting this, cuz it'll anger P3D. - noSmooth(); - } +// if (smooth) { +// smooth(); +// } else { +// // Don't bother setting this, cuz it'll anger P3D. +// noSmooth(); +// } if (textFont != null) { // System.out.println(" textFont in reapply is " + textFont); // textFont() resets the leading, so save it in case it's changed @@ -964,7 +980,65 @@ protected void reapplySettings() { blendMode(blendMode); - //reapplySettings = false; + reapplySettings = false; + } + + ////////////////////////////////////////////////////////////// + + // RENDERER STATE + + + protected void clearState() { // ignore + // Nothing to do here, it depends on the renderer's implementation. + } + + + protected void saveState() { // ignore + // Nothing to do here, it depends on the renderer's implementation. + } + + + protected void restoreState() { // ignore + // This method probably does not need to be re-implemented in the subclasses. All we need to + // do is to check for the resume in no-loop state situation: + restoredSurface = false; + if (!parent.looping) { + // The sketch needs to draw a few frames after resuming so it has the chance to restore the + // screen contents: + // https://github.com/processing/processing-android/issues/492 + // so we restart looping: + parent.loop(); + // and flag this situation when the surface has been restored: + restartedLoopingAfterResume = true; + } + } + + + protected boolean restoringState() { // ignore + return !restoredSurface && restartedLoopingAfterResume; + } + + + protected void restoreSurface() { // ignore + // When implementing this method in a subclass of PGraphics, it should add a call to the super + // implementation, to make sure that the looping is stopped in the case where the sketch was + // resumed in no-loop state (see comment in restoreState() method above). + if (restoredSurface && restartedLoopingAfterResume) { + restartedLoopingAfterResume = false; + parent.noLoop(); + } + } + + + protected boolean requestNoLoop() { // ignore + // Some renderers (OpenGL) cannot be set to no-loop right away, it has to be requested so + // any pending frames are properly rendered. Override as needed. + return false; + } + + + protected boolean isLooping() { // ignore + return parent.isLooping() && (!requestNoLoop() || !requestedNoLoop); } @@ -990,7 +1064,18 @@ protected void reapplySettings() { * turns off the z-buffer in the P3D or OPENGL renderers. *
    */ + @SuppressWarnings("deprecation") public void hint(int which) { + if (which == ENABLE_NATIVE_FONTS || + which == DISABLE_NATIVE_FONTS) { + showWarning("hint(ENABLE_NATIVE_FONTS) no longer supported. " + + "Use createFont() instead."); + } + if (which == ENABLE_KEY_REPEAT) { + parent.keyRepeatEnabled = true; + } else if (which == DISABLE_KEY_REPEAT) { + parent.keyRepeatEnabled = false; + } if (which > 0) { hints[which] = true; } else { @@ -1076,6 +1161,37 @@ public void normal(float nx, float ny, float nz) { } } + + public void attribPosition(String name, float x, float y, float z) { + showMissingWarning("attrib"); + } + + + public void attribNormal(String name, float nx, float ny, float nz) { + showMissingWarning("attrib"); + } + + + public void attribColor(String name, int color) { + showMissingWarning("attrib"); + } + + + public void attrib(String name, float... values) { + showMissingWarning("attrib"); + } + + + public void attrib(String name, int... values) { + showMissingWarning("attrib"); + } + + + public void attrib(String name, boolean... values) { + showMissingWarning("attrib"); + } + + /** * Set texture mode to either to use coordinates based on the IMAGE * (more intuitive for new users) or NORMALIZED (better for advanced chaps) @@ -1466,35 +1582,132 @@ public PShape loadShape(String filename) { } + public PShape loadShape(String filename, String options) { + showMissingWarning("loadShape"); + return null; + } + + ////////////////////////////////////////////////////////////// // SHAPE CREATION - public PShape createShape(PShape source) { - showMissingWarning("createShape"); - return null; + /** + * @webref shape + * @see PShape + * @see PShape#endShape() + * @see PApplet#loadShape(String) + */ + public PShape createShape() { + // Defaults to GEOMETRY (rather than GROUP like the default constructor) + // because that's how people will use it within a sketch. + return createShape(PShape.GEOMETRY); } - public PShape createShape() { - showMissingWarning("createShape"); - return null; + // POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, QUAD_STRIP + public PShape createShape(int type) { + // If it's a PRIMITIVE, it needs the 'params' field anyway + if (type == PConstants.GROUP || + type == PShape.PATH || + type == PShape.GEOMETRY) { + return createShapeFamily(type); + } + final String msg = + "Only GROUP, PShape.PATH, and PShape.GEOMETRY work with createShape()"; + throw new IllegalArgumentException(msg); } - public PShape createShape(int type) { - showMissingWarning("createShape"); - return null; + /** Override this method to return an appropriate shape for your renderer */ + protected PShape createShapeFamily(int type) { + return new PShape(this, type); +// showMethodWarning("createShape()"); +// return null; } + /** + * @param kind either POINT, LINE, TRIANGLE, QUAD, RECT, ELLIPSE, ARC, BOX, SPHERE + * @param p parameters that match the kind of shape + */ public PShape createShape(int kind, float... p) { - showMissingWarning("createShape"); - return null; + int len = p.length; + + if (kind == POINT) { + if (is3D() && len != 2 && len != 3) { + throw new IllegalArgumentException("Use createShape(POINT, x, y) or createShape(POINT, x, y, z)"); + } else if (len != 2) { + throw new IllegalArgumentException("Use createShape(POINT, x, y)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == LINE) { + if (is3D() && len != 4 && len != 6) { + throw new IllegalArgumentException("Use createShape(LINE, x1, y1, x2, y2) or createShape(LINE, x1, y1, z1, x2, y2, z1)"); + } else if (len != 4) { + throw new IllegalArgumentException("Use createShape(LINE, x1, y1, x2, y2)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == TRIANGLE) { + if (len != 6) { + throw new IllegalArgumentException("Use createShape(TRIANGLE, x1, y1, x2, y2, x3, y3)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == QUAD) { + if (len != 8) { + throw new IllegalArgumentException("Use createShape(QUAD, x1, y1, x2, y2, x3, y3, x4, y4)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == RECT) { + if (len != 4 && len != 5 && len != 8 && len != 9) { + throw new IllegalArgumentException("Wrong number of parameters for createShape(RECT), see the reference"); + } + return createShapePrimitive(kind, p); + + } else if (kind == ELLIPSE) { + if (len != 4 && len != 5) { + throw new IllegalArgumentException("Use createShape(ELLIPSE, x, y, w, h) or createShape(ELLIPSE, x, y, w, h, mode)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == ARC) { + if (len != 6 && len != 7) { + throw new IllegalArgumentException("Use createShape(ARC, x, y, w, h, start, stop)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == BOX) { + if (!is3D()) { + throw new IllegalArgumentException("createShape(BOX) is not supported in 2D"); + } else if (len != 1 && len != 3) { + throw new IllegalArgumentException("Use createShape(BOX, size) or createShape(BOX, width, height, depth)"); + } + return createShapePrimitive(kind, p); + + } else if (kind == SPHERE) { + if (!is3D()) { + throw new IllegalArgumentException("createShape(SPHERE) is not supported in 2D"); + } else if (len != 1) { + throw new IllegalArgumentException("Use createShape(SPHERE, radius)"); + } + return createShapePrimitive(kind, p); + } + throw new IllegalArgumentException("Unknown shape type passed to createShape()"); } + /** Override this to have a custom shape object used by your renderer. */ + protected PShape createShapePrimitive(int kind, float... p) { +// showMethodWarning("createShape()"); +// return null; + return new PShape(this, kind, p); + } + ////////////////////////////////////////////////////////////// @@ -1997,6 +2210,10 @@ protected void rectImpl(float x1, float y1, float x2, float y2, } + public void square(float x, float y, float extent) { + rect(x, y, extent, extent); + } + ////////////////////////////////////////////////////////////// @@ -2116,6 +2333,10 @@ protected void arcImpl(float x, float y, float w, float h, } + public void circle(float x, float y, float extent) { + ellipse(x, y, extent, extent); + } + ////////////////////////////////////////////////////////////// @@ -2363,11 +2584,10 @@ public void sphere(float r) { * endShape(); */ public float bezierPoint(float a, float b, float c, float d, float t) { - float t1 = 1.0f - t; - return a*t1*t1*t1 + 3*b*t*t1*t1 + 3*c*t*t*t1 + d*t*t*t; + float t1 = t-1.0f; + return t * ( 3*t1*(b*t1-c*t) + d*t*t ) - a*t1*t1*t1; } - /** * Provide the tangent at the given point on the bezier curve. * Fix from davbol for 0136. @@ -2643,26 +2863,37 @@ protected void splineForward(int segments, PMatrix3D matrix) { // SMOOTHING - /** - * If true in PImage, use bilinear interpolation for copy() - * operations. When inherited by PGraphics, also controls shapes. - */ - public void smooth() { - smooth = true; + public void smooth() { // ignore + smooth(1); } - public void smooth(int level) { - smooth = true; + + public void smooth(int quality) { // ignore + if (primaryGraphics) { + parent.smooth(quality); + } else { + // for createGraphics(), make sure beginDraw() not called yet + if (settingsInited) { + // ignore if it's just a repeat of the current state + if (this.smooth != quality) { + smoothWarning("smooth"); + } + } else { + this.smooth = quality; + } + } } - /** - * Disable smoothing. See smooth(). - */ - public void noSmooth() { - smooth = false; + + public void noSmooth() { // ignore + smooth(0); } + private void smoothWarning(String method) { + PGraphics.showWarning("%s() can only be used before beginDraw()", method); + } + ////////////////////////////////////////////////////////////// @@ -2965,10 +3196,47 @@ public float textDescent() { * The leading will also be reset. */ public void textFont(PFont which) { - if (which != null) { - textFont = which; + if (which == null) { + throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT); + } + textFontImpl(which, which.getDefaultSize()); + } + + + /** + * Useful function to set the font and size at the same time. + */ + public void textFont(PFont which, float size) { + if (which == null) { + throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT); + } + // https://github.com/processing/processing/issues/3110 + if (size <= 0) { + // Using System.err instead of showWarning to avoid running out of + // memory with a bunch of textSize() variants (cause of this bug is + // usually something done with map() or in a loop). + System.err.println("textFont: ignoring size " + size + " px:" + + "the text size must be larger than zero"); + size = textSize; + } + textFontImpl(which, size); + } + + + /** + * Called from textFont. Check the validity of args and + * print possible errors to the user before calling this. + * Subclasses will want to override this one. + * + * @param which font to set, not null + * @param size size to set, greater than zero + */ + protected void textFontImpl(PFont which, float size) { + textFont = which; // if (hints[ENABLE_NATIVE_FONTS]) { -// which.findTypeface(name); +// //if (which.font == null) { +// which.findNative(); +// //} // } /* textFontNative = which.font; @@ -2993,20 +3261,8 @@ public void textFont(PFont which) { // float w = font.getStringBounds(text, g2.getFontRenderContext()).getWidth(); } */ - textSize(which.size); - } else { - throw new RuntimeException(ERROR_TEXTFONT_NULL_PFONT); - } - } - - - /** - * Useful function to set the font and size at the same time. - */ - public void textFont(PFont which, float size) { - textFont(which); - textSize(size); + handleTextSize(size); } @@ -3059,15 +3315,40 @@ protected boolean textModeCheck(int mode) { * Sets the text size, also resets the value for the leading. */ public void textSize(float size) { + // https://github.com/processing/processing/issues/3110 + if (size <= 0) { + // Using System.err instead of showWarning to avoid running out of + // memory with a bunch of textSize() variants (cause of this bug is + // usually something done with map() or in a loop). + System.err.println("textSize(" + size + ") ignored: " + + "the text size must be larger than zero"); + return; + } if (textFont == null) { defaultFontOrDeath("textSize", size); } + textSizeImpl(size); + } + + + /** + * Called from textSize() after validating size. Subclasses + * will want to override this one. + * @param size size of the text, greater than zero + */ + protected void textSizeImpl(float size) { + handleTextSize(size); + } + + /** + * Sets the actual size. Called from textSizeImpl and + * from textFontImpl after setting the font. + * @param size size of the text, greater than zero + */ + protected void handleTextSize(float size) { textSize = size; -// PApplet.println("textSize textAscent -> " + textAscent()); -// PApplet.println("textSize textDescent -> " + textDescent()); textLeading = (textAscent() + textDescent()) * 1.275f; -// PApplet.println("textSize textLeading = " + textLeading); } @@ -3113,6 +3394,11 @@ public float textWidth(String str) { } + public float textWidth(char[] chars, int start, int length) { + return textWidthImpl(chars, start, start + length); + } + + /** * Implementation of returning the text width of * the chars [start, stop) in the buffer. @@ -3183,14 +3469,6 @@ public void text(char c, float x, float y, float z) { } -// /** -// * Write text where we just left off. -// */ -// public void text(String str) { -// text(str, textX, textY, textZ); -// } - - /** * Draw a chunk of text. * Newlines that are \n (Unix newline or linefeed char, ascii 10) @@ -3248,6 +3526,52 @@ public void text(String str, float x, float y) { } + /** + * Method to draw text from an array of chars. This method will usually be + * more efficient than drawing from a String object, because the String will + * not be converted to a char array before drawing. + */ + public void text(char[] chars, int start, int stop, float x, float y) { + // If multiple lines, sum the height of the additional lines + float high = 0; //-textAscent(); + for (int i = start; i < stop; i++) { + if (chars[i] == '\n') { + high += textLeading; + } + } + if (textAlignY == CENTER) { + // for a single line, this adds half the textAscent to y + // for multiple lines, subtract half the additional height + //y += (textAscent() - textDescent() - high)/2; + y += (textAscent() - high)/2; + } else if (textAlignY == TOP) { + // for a single line, need to add textAscent to y + // for multiple lines, no different + y += textAscent(); + } else if (textAlignY == BOTTOM) { + // for a single line, this is just offset by the descent + // for multiple lines, subtract leading for each line + y -= textDescent() + high; + //} else if (textAlignY == BASELINE) { + // do nothing + } + +// int start = 0; + int index = 0; + while (index < stop) { //length) { + if (chars[index] == '\n') { + textLineAlignImpl(chars, start, index, x, y); + start = index + 1; + y += textLeading; + } + index++; + } + if (start < stop) { //length) { + textLineAlignImpl(chars, start, index, x, y); + } + } + + /** * Same as above but with a z coordinate. */ @@ -3266,6 +3590,17 @@ public void text(String str, float x, float y, float z) { } + public void text(char[] chars, int start, int stop, + float x, float y, float z) { + if (z != 0) translate(0, 0, z); // slow! + + text(chars, start, stop, x, y); +// textZ = z; + + if (z != 0) translate(0, 0, -z); // inaccurate! + } + + /** * Draw text in a box that is constrained to a particular size. * The current rectMode() determines what the coordinates mean @@ -3397,8 +3732,8 @@ public void text(String str, float x1, float y1, float x2, float y2) { * Emit a sentence of text, defined as a chunk of text without any newlines. * @param stop non-inclusive, the end of the text in question */ - private boolean textSentence(char[] buffer, int start, int stop, - float boxWidth, float spaceWidth) { + protected boolean textSentence(char[] buffer, int start, int stop, + float boxWidth, float spaceWidth) { float runningX = 0; // Keep track of this separately from index, since we'll need to back up @@ -3463,7 +3798,7 @@ private boolean textSentence(char[] buffer, int start, int stop, } - private void textSentenceBreak(int start, int stop) { + protected void textSentenceBreak(int start, int stop) { if (textBreakCount == textBreakStart.length) { textBreakStart = PApplet.expand(textBreakStart); textBreakStop = PApplet.expand(textBreakStop); @@ -3592,6 +3927,22 @@ protected void textCharModelImpl(PImage glyph, } + ////////////////////////////////////////////////////////////// + + // PARITY WITH P5.JS + + + public void push() { + pushStyle(); + pushMatrix(); + } + + + public void pop() { + popStyle(); + popMatrix(); + } + ////////////////////////////////////////////////////////////// @@ -3818,6 +4169,46 @@ public PMatrix3D getMatrix(PMatrix3D target) { } + /** + * Returns a copy of the current object matrix. + * Pass in null to create a new matrix. + */ + public PMatrix3D getObjectMatrix() { + showMissingWarning("getObjectMatrix"); + return null; + } + + + /** + * Copy the current object matrix into the specified target. + * Pass in null to create a new matrix. + */ + public PMatrix3D getObjectMatrix(PMatrix3D target) { + showMissingWarning("getObjectMatrix"); + return null; + } + + + /** + * Returns a copy of the current eye matrix. + * Pass in null to create a new matrix. + */ + public PMatrix3D getEyeMatrix() { + showMissingWarning("getEyeMatrix"); + return null; + } + + + /** + * Copy the current eye matrix into the specified target. + * Pass in null to create a new matrix. + */ + public PMatrix3D getEyeMatrix(PMatrix3D target) { + showMissingWarning("getEyeMatrix"); + return null; + } + + /** * Set the current transformation matrix to the contents of another. */ @@ -3859,6 +4250,11 @@ public void printMatrix() { // CAMERA + public void cameraUp() { + showMethodWarning("cameraUp"); + } + + public void beginCamera() { showMethodWarning("beginCamera"); } @@ -3886,6 +4282,10 @@ public void printCamera() { } + public void eye() { + showMethodWarning("eye"); + } + ////////////////////////////////////////////////////////////// @@ -4034,6 +4434,69 @@ public float modelZ(float x, float y, float z) { } + ////////////////////////////////////////////////////////////// + + // RAY CASTING + + + public PVector[] getRayFromScreen(float screenX, float screenY, PVector[] ray) { + showMissingWarning("getRayFromScreen"); + return null; + } + + + public void getRayFromScreen(float screenX, float screenY, PVector origin, PVector direction) { + showMissingWarning("getRayFromScreen"); + } + + + public boolean intersectsSphere(float r, float screenX, float screenY) { + showMissingWarning("intersectsSphere"); + return false; + } + + + public boolean intersectsSphere(float r, PVector origin, PVector direction) { + showMissingWarning("intersectsSphere"); + return false; + } + + + public boolean intersectsBox(float size, float screenX, float screenY) { + showMissingWarning("intersectsBox"); + return false; + } + + + public boolean intersectsBox(float w, float h, float d, float screenX, float screenY) { + showMissingWarning("intersectsBox"); + return false; + } + + + public boolean intersectsBox(float size, PVector origin, PVector direction) { + showMissingWarning("intersectsBox"); + return false; + } + + + public boolean intersectsBox(float w, float h, float d, PVector origin, PVector direction) { + showMissingWarning("intersectsBox"); + return false; + } + + + public PVector intersectsPlane(float screenX, float screenY) { + showMissingWarning("intersectsPlane"); + return null; + } + + + public PVector intersectsPlane(PVector origin, PVector direction) { + showMissingWarning("intersectsPlane"); + return null; + } + ////////////////////////////////////////////////////////////// @@ -4073,6 +4536,8 @@ public void style(PStyle s) { ellipseMode(s.ellipseMode); shapeMode(s.shapeMode); + blendMode(s.blendMode); + if (s.tint) { tint(s.tintColor); } else { @@ -4151,6 +4616,8 @@ public PStyle getStyle(PStyle s) { // ignore s.ellipseMode = ellipseMode; s.shapeMode = shapeMode; + s.blendMode = blendMode; + s.colorMode = colorMode; s.colorModeX = colorModeX; s.colorModeY = colorModeY; @@ -5235,13 +5702,16 @@ public int lerpColor(int c1, int c2, float amt) { static float[] lerpColorHSB1; static float[] lerpColorHSB2; - static float[] lerpColorHSB3; + static float[] lerpColorHSB3; /** * Interpolate between two colors. Like lerp(), but for the * individual color components of a color supplied as an int value. */ static public int lerpColor(int c1, int c2, float amt, int mode) { + if (amt < 0) amt = 0; + if (amt > 1) amt = 1; + if (mode == RGB) { float a1 = ((c1 >> 24) & 0xff); float r1 = (c1 >> 16) & 0xff; @@ -5252,10 +5722,10 @@ static public int lerpColor(int c1, int c2, float amt, int mode) { float g2 = (c2 >> 8) & 0xff; float b2 = c2 & 0xff; - return (((int) (a1 + (a2-a1)*amt) << 24) | - ((int) (r1 + (r2-r1)*amt) << 16) | - ((int) (g1 + (g2-g1)*amt) << 8) | - ((int) (b1 + (b2-b1)*amt))); + return ((PApplet.round(a1 + (a2-a1)*amt) << 24) | + (PApplet.round(r1 + (r2-r1)*amt) << 16) | + (PApplet.round(g1 + (g2-g1)*amt) << 8) | + (PApplet.round(b1 + (b2-b1)*amt))); } else if (mode == HSB) { if (lerpColorHSB1 == null) { @@ -5266,7 +5736,7 @@ static public int lerpColor(int c1, int c2, float amt, int mode) { float a1 = (c1 >> 24) & 0xff; float a2 = (c2 >> 24) & 0xff; - int alfa = ((int) (a1 + (a2-a1)*amt)) << 24; + int alfa = (PApplet.round(a1 + (a2-a1)*amt)) << 24; Color.RGBToHSV((c1 >> 16) & 0xff, (c1 >> 8) & 0xff, c1 & 0xff, lerpColorHSB1); @@ -5298,23 +5768,21 @@ static public int lerpColor(int c1, int c2, float amt, int mode) { } float ho = (PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt)) % 1.0f; */ + // float ho = PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt); + // float so = PApplet.lerp(lerpColorHSB1[1], lerpColorHSB2[1], amt); + // float bo = PApplet.lerp(lerpColorHSB1[2], lerpColorHSB2[2], amt); -// float ho = PActivity.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt); -// float so = PActivity.lerp(lerpColorHSB1[1], lerpColorHSB2[1], amt); -// float bo = PActivity.lerp(lerpColorHSB1[2], lerpColorHSB2[2], amt); -// return alfa | (Color.HSVtoRGB(ho, so, bo) & 0xFFFFFF); -// return Color.HSVToColor(alfa, new float[] { ho, so, bo }); + // return alfa | (Color.RGBToHSV(ho, so, bo) & 0xFFFFFF); lerpColorHSB3[0] = PApplet.lerp(lerpColorHSB1[0], lerpColorHSB2[0], amt); lerpColorHSB3[1] = PApplet.lerp(lerpColorHSB1[1], lerpColorHSB2[1], amt); lerpColorHSB3[2] = PApplet.lerp(lerpColorHSB1[2], lerpColorHSB2[2], amt); - return Color.HSVToColor(alfa, lerpColorHSB3); + return Color.HSVToColor(alfa, lerpColorHSB3); } return 0; } - ////////////////////////////////////////////////////////////// // BEGINRAW/ENDRAW @@ -5510,4 +5978,158 @@ public boolean is3D() { public boolean isGL() { return false; } + + + ////////////////////////////////////////////////////////////// + + // ASYNC IMAGE SAVING + + + @Override + public boolean save(String filename) { // ignore + + if (hints[DISABLE_ASYNC_SAVEFRAME]) { + return super.save(filename); + } + + if (asyncImageSaver == null) { + asyncImageSaver = new AsyncImageSaver(); + } + + if (!loaded) loadPixels(); + PImage target = asyncImageSaver.getAvailableTarget(pixelWidth, pixelHeight, + format); + if (target == null) return false; + int count = PApplet.min(pixels.length, target.pixels.length); + System.arraycopy(pixels, 0, target.pixels, 0, count); + asyncImageSaver.saveTargetAsync(this, target, filename); + + return true; + } + + protected void processImageBeforeAsyncSave(PImage image) { } + + + protected static AsyncImageSaver asyncImageSaver; + + protected static class AsyncImageSaver { + + static final int TARGET_COUNT = + Math.max(1, Runtime.getRuntime().availableProcessors() - 1); + + BlockingQueue targetPool = new ArrayBlockingQueue<>(TARGET_COUNT); + ExecutorService saveExecutor = Executors.newFixedThreadPool(TARGET_COUNT); + + int targetsCreated = 0; + + + static final int TIME_AVG_FACTOR = 32; + + volatile long avgNanos = 0; + long lastTime = 0; + int lastFrameCount = 0; + + + public AsyncImageSaver() { } // ignore + + + public void dispose() { // ignore + saveExecutor.shutdown(); + try { + saveExecutor.awaitTermination(5000, TimeUnit.SECONDS); + } catch (InterruptedException e) { } + } + + + public boolean hasAvailableTarget() { // ignore + return targetsCreated < TARGET_COUNT || targetPool.isEmpty(); + } + + + /** + * After taking a target, you must call saveTargetAsync() or + * returnUnusedTarget(), otherwise one thread won't be able to run + */ + public PImage getAvailableTarget(int requestedWidth, int requestedHeight, // ignore + int format) { + try { + PImage target; + if (targetsCreated < TARGET_COUNT && targetPool.isEmpty()) { + target = new PImage(requestedWidth, requestedHeight); + targetsCreated++; + } else { + target = targetPool.take(); + if (target.width != requestedWidth || + target.height != requestedHeight) { + target.width = requestedWidth; + target.height = requestedHeight; + // TODO: this kills performance when saving different sizes + target.pixels = new int[requestedWidth * requestedHeight]; + } + } + target.format = format; + return target; + } catch (InterruptedException e) { + return null; + } + } + + + public void returnUnusedTarget(PImage target) { // ignore + targetPool.offer(target); + } + + + public void saveTargetAsync(final PGraphics renderer, final PImage target, // ignore + final String filename) { + target.parent = renderer.parent; + + // if running every frame, smooth the framerate + if (target.parent.frameCount - 1 == lastFrameCount && TARGET_COUNT > 1) { + + // count with one less thread to reduce jitter + // 2 cores - 1 save thread - no wait + // 4 cores - 3 save threads - wait 1/2 of save time + // 8 cores - 7 save threads - wait 1/6 of save time + long avgTimePerFrame = avgNanos / (Math.max(1, TARGET_COUNT - 1)); + long now = System.nanoTime(); + long delay = PApplet.round((lastTime + avgTimePerFrame - now) / 1e6f); + try { + if (delay > 0) Thread.sleep(delay); + } catch (InterruptedException e) { } + } + + lastFrameCount = target.parent.frameCount; + lastTime = System.nanoTime(); + + try { + saveExecutor.submit(new Runnable() { + @Override + public void run() { // ignore + try { + long startTime = System.nanoTime(); + renderer.processImageBeforeAsyncSave(target); + target.save(filename); + long saveNanos = System.nanoTime() - startTime; + synchronized (AsyncImageSaver.this) { + if (avgNanos == 0) { + avgNanos = saveNanos; + } else if (saveNanos < avgNanos) { + avgNanos = (avgNanos * (TIME_AVG_FACTOR - 1) + saveNanos) / + (TIME_AVG_FACTOR); + } else { + avgNanos = saveNanos; + } + } + } finally { + targetPool.offer(target); + } + } + }); + } catch (RejectedExecutionException e) { + // the executor service was probably shut down, no more saving for us + } + } + } + } diff --git a/core/src/processing/core/PImage.java b/libs/processing-core/src/main/java/processing/core/PImage.java similarity index 79% rename from core/src/processing/core/PImage.java rename to libs/processing-core/src/main/java/processing/core/PImage.java index a75ebb07b..8eceaf2c4 100644 --- a/core/src/processing/core/PImage.java +++ b/libs/processing-core/src/main/java/processing/core/PImage.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2004-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or @@ -51,6 +52,13 @@ public class PImage implements PConstants, Cloneable { public int[] pixels; public int width, height; + /** + * For the time being, simply to ensure compatibility with Java mode code + */ + public int pixelDensity = 1; + public int pixelWidth; + public int pixelHeight; + /** * Path to parent object that will be used with save(). * This prevents users from needing savePath() to use PImage.save(). @@ -148,6 +156,10 @@ public void init(int width, int height, int format) { // ignore this.pixels = new int[width*height]; this.format = format; // this.cache = null; + + pixelWidth = width * pixelDensity; + pixelHeight = height * pixelDensity; + this.pixels = new int[pixelWidth * pixelHeight]; } @@ -182,6 +194,9 @@ public PImage(Object nativeObject) { this.height = bitmap.getHeight(); this.pixels = null; this.format = bitmap.hasAlpha() ? ARGB : RGB; + this.pixelDensity = 1; + this.pixelWidth = width; + this.pixelHeight = height; } @@ -193,6 +208,13 @@ public Object getNative() { } + public void setNative(Object nativeObject) { + Bitmap bitmap = (Bitmap) nativeObject; + this.bitmap = bitmap; + } + + + ////////////////////////////////////////////////////////////// // MARKING IMAGE AS MODIFIED / FOR USE w/ GET/SET @@ -242,9 +264,24 @@ public void loadPixels() { // ignore if (pixels == null || pixels.length != width*height) { pixels = new int[width*height]; } + if (bitmap != null) { - bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + if (modified) { + // The pixels array has been used to do color manipulations, so + // the bitmap should be updated + if (!bitmap.isMutable()) { + // create a mutable version of this bitmap + bitmap = bitmap.copy(Config.ARGB_8888, true); + } + bitmap.setPixels(pixels, 0, width, mx1, my1, mx2 - mx1, my2 - my1); + modified = false; + } else { + // Get wherever it is in the bitmap right now, we assume is the most + // up-to-date version of the image. + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + } } + setLoaded(); } @@ -336,6 +373,10 @@ public Object clone() throws CloneNotSupportedException { // ignore * Use 0 for wide or high to make that dimension scale proportionally. */ public void resize(int w, int h) { // ignore + if (bitmap == null) { + return; // Cannot resize an image not backed by a bitmap + } + if (w <= 0 && h <= 0) { throw new IllegalArgumentException("width or height must be > 0 for resize"); } @@ -348,11 +389,15 @@ public void resize(int w, int h) { // ignore h = (int) (height * diff); } bitmap = Bitmap.createScaledBitmap(bitmap, w, h, true); + if (pixels != null) { + // Resize pixels array, if in use. + pixels = new int[w * h]; + bitmap.getPixels(pixels, 0, w, 0, 0, w, h); + } this.width = w; this.height = h; - - // Mark the pixels array as altered - updatePixels(); + this.pixelWidth = w * pixelDensity; + this.pixelHeight = h * pixelDensity; } @@ -476,6 +521,8 @@ public PImage get(int x, int y, int w, int h) { target.parent = parent; // parent may be null so can't use createImage() if (w > 0 && h > 0) { getImpl(x, y, w, h, target, targetX, targetY); + Bitmap nat = Bitmap.createBitmap(target.pixels, targetWidth, targetHeight, Config.ARGB_8888); + target.setNative(nat); } return target; } @@ -490,11 +537,11 @@ public PImage get(int x, int y, int w, int h) { protected void getImpl(int sourceX, int sourceY, int sourceWidth, int sourceHeight, PImage target, int targetX, int targetY) { - if (pixels == null) { + if (bitmap != null) { bitmap.getPixels(target.pixels, targetY*target.width + targetX, target.width, sourceX, sourceY, sourceWidth, sourceHeight); - } else { + } else if (pixels != null) { int sourceIndex = sourceY*width + sourceX; int targetIndex = targetY*target.width + targetX; for (int row = 0; row < sourceHeight; row++) { @@ -516,6 +563,11 @@ public PImage get() { } + public PImage copy() { + return get(0, 0, pixelWidth, pixelHeight); + } + + /** * Set a single pixel to the specified color. */ @@ -1379,24 +1431,6 @@ public void blend(int sx, int sy, int sw, int sh, public void blend(PImage src, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh, int mode) { - /* - if (imageMode == CORNER) { // if CORNERS, do nothing - sx2 += sx1; - sy2 += sy1; - dx2 += dx1; - dy2 += dy1; - - } else if (imageMode == CENTER) { - sx1 -= sx2 / 2f; - sy1 -= sy2 / 2f; - sx2 += sx1; - sy2 += sy1; - dx1 -= dx2 / 2f; - dy1 -= dy2 / 2f; - dx2 += dx1; - dy2 += dy1; - } - */ int sx2 = sx + sw; int sy2 = sy + sh; int dx2 = dx + dw; @@ -1405,18 +1439,18 @@ public void blend(PImage src, loadPixels(); if (src == this) { if (intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) { - blit_resize(get(sx, sy, sx2 - sx, sy2 - sy), - 0, 0, sx2 - sx - 1, sy2 - sy - 1, - pixels, width, height, dx, dy, dx2, dy2, mode); + blit_resize(get(sx, sy, sw, sh), + 0, 0, sw, sh, + pixels, pixelWidth, pixelHeight, dx, dy, dx2, dy2, mode); } else { // same as below, except skip the loadPixels() because it'd be redundant blit_resize(src, sx, sy, sx2, sy2, - pixels, width, height, dx, dy, dx2, dy2, mode); + pixels, pixelWidth, pixelHeight, dx, dy, dx2, dy2, mode); } } else { src.loadPixels(); blit_resize(src, sx, sy, sx2, sy2, - pixels, width, height, dx, dy, dx2, dy2, mode); + pixels, pixelWidth, pixelHeight, dx, dy, dx2, dy2, mode); //src.updatePixels(); } updatePixels(); @@ -1474,8 +1508,8 @@ private void blit_resize(PImage img, int mode) { if (srcX1 < 0) srcX1 = 0; if (srcY1 < 0) srcY1 = 0; - if (srcX2 > img.width) srcX2 = img.width; - if (srcY2 > img.height) srcY2 = img.height; + if (srcX2 > img.pixelWidth) srcX2 = img.pixelWidth; + if (srcY2 > img.pixelHeight) srcY2 = img.pixelHeight; int srcW = srcX2 - srcX1; int srcH = srcY2 - srcY1; @@ -1491,15 +1525,15 @@ private void blit_resize(PImage img, if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW || destY1 >= screenH || - srcX1 >= img.width || srcY1 >= img.height) { + srcX1 >= img.pixelWidth || srcY1 >= img.pixelHeight) { return; } int dx = (int) (srcW / (float) destW * PRECISIONF); int dy = (int) (srcH / (float) destH * PRECISIONF); - srcXOffset = (int) (destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF); - srcYOffset = (int) (destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF); + srcXOffset = destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF; + srcYOffset = destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF; if (destX1 < 0) { destW += destX1; @@ -1510,17 +1544,17 @@ private void blit_resize(PImage img, destY1 = 0; } - destW = low(destW, screenW - destX1); - destH = low(destH, screenH - destY1); + destW = min(destW, screenW - destX1); + destH = min(destH, screenH - destY1); int destOffset = destY1 * screenW + destX1; srcBuffer = img.pixels; if (smooth) { // use bilinear filtering - iw = img.width; - iw1 = img.width - 1; - ih1 = img.height - 1; + iw = img.pixelWidth; + iw1 = img.pixelWidth - 1; + ih1 = img.pixelHeight - 1; switch (mode) { @@ -1729,12 +1763,12 @@ private void blit_resize(PImage img, case BLEND: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { // davbol - renamed old blend_multiply to blend_blend destPixels[destOffset + x] = blend_blend(destPixels[destOffset + x], - srcBuffer[sY + (sX >> PRECISIONB)]); + srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; @@ -1745,7 +1779,7 @@ private void blit_resize(PImage img, case ADD: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_add_pin(destPixels[destOffset + x], @@ -1760,7 +1794,7 @@ private void blit_resize(PImage img, case SUBTRACT: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_sub_pin(destPixels[destOffset + x], @@ -1775,7 +1809,7 @@ private void blit_resize(PImage img, case LIGHTEST: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_lightest(destPixels[destOffset + x], @@ -1790,7 +1824,7 @@ private void blit_resize(PImage img, case DARKEST: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_darkest(destPixels[destOffset + x], @@ -1805,7 +1839,7 @@ private void blit_resize(PImage img, case REPLACE: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = srcBuffer[sY + (sX >> PRECISIONB)]; sX += dx; @@ -1818,7 +1852,7 @@ private void blit_resize(PImage img, case DIFFERENCE: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_difference(destPixels[destOffset + x], @@ -1833,7 +1867,7 @@ private void blit_resize(PImage img, case EXCLUSION: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_exclusion(destPixels[destOffset + x], @@ -1848,7 +1882,7 @@ private void blit_resize(PImage img, case MULTIPLY: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_multiply(destPixels[destOffset + x], @@ -1863,7 +1897,7 @@ private void blit_resize(PImage img, case SCREEN: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_screen(destPixels[destOffset + x], @@ -1878,7 +1912,7 @@ private void blit_resize(PImage img, case OVERLAY: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_overlay(destPixels[destOffset + x], @@ -1893,7 +1927,7 @@ private void blit_resize(PImage img, case HARD_LIGHT: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_hard_light(destPixels[destOffset + x], @@ -1908,7 +1942,7 @@ private void blit_resize(PImage img, case SOFT_LIGHT: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_soft_light(destPixels[destOffset + x], @@ -1924,7 +1958,7 @@ private void blit_resize(PImage img, case DODGE: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_dodge(destPixels[destOffset + x], @@ -1939,7 +1973,7 @@ private void blit_resize(PImage img, case BURN: for (int y = 0; y < destH; y++) { sX = srcXOffset; - sY = (srcYOffset >> PRECISIONB) * img.width; + sY = (srcYOffset >> PRECISIONB) * img.pixelWidth; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_burn(destPixels[destOffset + x], @@ -1959,21 +1993,21 @@ private void blit_resize(PImage img, private void filter_new_scanline() { sX = srcXOffset; fracV = srcYOffset & PREC_MAXVAL; - ifV = PREC_MAXVAL - fracV; + ifV = PREC_MAXVAL - fracV + 1; v1 = (srcYOffset >> PRECISIONB) * iw; - v2 = low((srcYOffset >> PRECISIONB) + 1, ih1) * iw; + v2 = min((srcYOffset >> PRECISIONB) + 1, ih1) * iw; } private int filter_bilinear() { fracU = sX & PREC_MAXVAL; - ifU = PREC_MAXVAL - fracU; + ifU = PREC_MAXVAL - fracU + 1; ul = (ifU * ifV) >> PRECISIONB; - ll = (ifU * fracV) >> PRECISIONB; - ur = (fracU * ifV) >> PRECISIONB; - lr = (fracU * fracV) >> PRECISIONB; + ll = ifU - ul; + ur = ifV - ul; + lr = PREC_MAXVAL + 1 - ul - ll - ur; u1 = (sX >> PRECISIONB); - u2 = low(u1 + 1, iw1); + u2 = min(u1 + 1, iw1); // get color values of the 4 neighbouring texels cUL = srcBuffer[v1 + u1]; @@ -2007,333 +2041,479 @@ private int filter_bilinear() { // internal blending methods - private static int low(int a, int b) { + private static int min(int a, int b) { return (a < b) ? a : b; } - private static int high(int a, int b) { + private static int max(int a, int b) { return (a > b) ? a : b; } - // davbol - added peg helper, equiv to constrain(n,0,255) - private static int peg(int n) { - return (n < 0) ? 0 : ((n > 255) ? 255 : n); - } - - private static int mix(int a, int b, int f) { - return a + (((b - a) * f) >> 8); - } + ///////////////////////////////////////////////////////////// + // BLEND MODE IMPLEMENTATIONS - ///////////////////////////////////////////////////////////// + /* + * Jakub Valtar + * + * All modes use SRC alpha to interpolate between DST and the result of + * the operation: + * + * R = (1 - SRC_ALPHA) * DST + SRC_ALPHA * + * + * Comments above each mode only specify the formula of its operation. + * + * These implementations treat alpha 127 (=255/2) as a perfect 50 % mix. + * + * One alpha value between 126 and 127 is intentionally left out, + * so the step 126 -> 127 is twice as big compared to other steps. + * This is because our colors are in 0..255 range, but we divide + * by right shifting 8 places (=256) which is much faster than + * (correct) float division by 255.0f. The missing value was placed + * between 126 and 127, because limits of the range (near 0 and 255) and + * the middle value (127) have to blend correctly. + * + * Below you will often see RED and BLUE channels (RB) manipulated together + * and GREEN channel (GN) manipulated separately. It is sometimes possible + * because the operation won't use more than 16 bits, so we process the RED + * channel in the upper 16 bits and BLUE channel in the lower 16 bits. This + * decreases the number of operations per pixel and thus makes things faster. + * + * Some of the modes are hand tweaked (various +1s etc.) to be more accurate + * and to produce correct values in extremes. Below is a sketch you can use + * to check any blending function for + * + * 1) Discrepancies between color channels: + * - highlighted by the offending color + * 2) Behavior at extremes (set colorCount to 256): + * - values of all corners are printed to the console + * 3) Rounding errors: + * - set colorCount to lower value to better see color bands + * - // BLEND MODE IMPLEMENTIONS +// use powers of 2 in range 2..256 +// to better see color bands +final int colorCount = 256; +final int blockSize = 3; - private static int blend_blend(int a, int b) { - int f = (b & ALPHA_MASK) >>> 24; +void settings() { + size(blockSize * 256, blockSize * 256); +} - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - mix(a & RED_MASK, b & RED_MASK, f) & RED_MASK | - mix(a & GREEN_MASK, b & GREEN_MASK, f) & GREEN_MASK | - mix(a & BLUE_MASK, b & BLUE_MASK, f)); +void setup() { } + +void draw() { + noStroke(); + colorMode(RGB, colorCount-1); + int alpha = (mouseX / blockSize) << 24; + int r, g, b, r2, g2, b2 = 0; + for (int x = 0; x <= 0xFF; x++) { + for (int y = 0; y <= 0xFF; y++) { + int dst = (x << 16) | (x << 8) | x; + int src = (y << 16) | (y << 8) | y | alpha; + int result = testFunction(dst, src); + r = r2 = (result >> 16 & 0xFF); + g = g2 = (result >> 8 & 0xFF); + b = b2 = (result >> 0 & 0xFF); + if (r != g && r != b) r2 = (128 + r2) % 255; + if (g != r && g != b) g2 = (128 + g2) % 255; + if (b != r && b != g) b2 = (128 + b2) % 255; + fill(r2 % colorCount, g2 % colorCount, b2 % colorCount); + rect(x * blockSize, y * blockSize, blockSize, blockSize); + } } + println( + "alpha:", mouseX/blockSize, + "TL:", hex(get(0, 0)), + "TR:", hex(get(width-1, 0)), + "BR:", hex(get(width-1, height-1)), + "BL:", hex(get(0, height-1))); +} + +int testFunction(int dst, int src) { + // your function here + return dst; +} + + * + * + */ + private static final int RB_MASK = 0x00FF00FF; + private static final int GN_MASK = 0x0000FF00; /** - * additive blend with clipping + * Blend + * O = S */ - private static int blend_add_pin(int a, int b) { - int f = (b & ALPHA_MASK) >>> 24; + private static int blend_blend(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - low(((a & RED_MASK) + - ((b & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | - low(((a & GREEN_MASK) + - ((b & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | - low((a & BLUE_MASK) + - (((b & BLUE_MASK) * f) >> 8), BLUE_MASK)); + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + (src & RB_MASK) * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + (src & GN_MASK) * s_a) >>> 8 & GN_MASK; } /** - * subtractive blend with clipping + * Add + * O = MIN(D + S, 1) */ - private static int blend_sub_pin(int a, int b) { - int f = (b & ALPHA_MASK) >>> 24; + private static int blend_add_pin(int dst, int src) { + int a = src >>> 24; - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - high(((a & RED_MASK) - ((b & RED_MASK) >> 8) * f), - GREEN_MASK) & RED_MASK | - high(((a & GREEN_MASK) - ((b & GREEN_MASK) >> 8) * f), - BLUE_MASK) & GREEN_MASK | - high((a & BLUE_MASK) - (((b & BLUE_MASK) * f) >> 8), 0)); + int s_a = a + (a >= 0x7F ? 1 : 0); + + int rb = (dst & RB_MASK) + ((src & RB_MASK) * s_a >>> 8 & RB_MASK); + int gn = (dst & GN_MASK) + ((src & GN_MASK) * s_a >>> 8); + + return min((dst >>> 24) + a, 0xFF) << 24 | + min(rb & 0xFFFF0000, RED_MASK) | + min(gn & 0x00FFFF00, GREEN_MASK) | + min(rb & 0x0000FFFF, BLUE_MASK); } /** - * only returns the blended lightest colour + * Subtract + * O = MAX(0, D - S) */ - private static int blend_lightest(int a, int b) { - int f = (b & ALPHA_MASK) >>> 24; + private static int blend_sub_pin(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - high(a & RED_MASK, ((b & RED_MASK) >> 8) * f) & RED_MASK | - high(a & GREEN_MASK, ((b & GREEN_MASK) >> 8) * f) & GREEN_MASK | - high(a & BLUE_MASK, ((b & BLUE_MASK) * f) >> 8)); + int rb = ((src & RB_MASK) * s_a >>> 8); + int gn = ((src & GREEN_MASK) * s_a >>> 8); + + return min((dst >>> 24) + a, 0xFF) << 24 | + max((dst & RED_MASK) - (rb & RED_MASK), 0) | + max((dst & GREEN_MASK) - (gn & GREEN_MASK), 0) | + max((dst & BLUE_MASK) - (rb & BLUE_MASK), 0); } /** - * only returns the blended darkest colour + * Lightest + * O = MAX(D, S) */ - private static int blend_darkest(int a, int b) { - int f = (b & ALPHA_MASK) >>> 24; + private static int blend_lightest(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - mix(a & RED_MASK, - low(a & RED_MASK, - ((b & RED_MASK) >> 8) * f), f) & RED_MASK | - mix(a & GREEN_MASK, - low(a & GREEN_MASK, - ((b & GREEN_MASK) >> 8) * f), f) & GREEN_MASK | - mix(a & BLUE_MASK, - low(a & BLUE_MASK, - ((b & BLUE_MASK) * f) >> 8), f)); + int rb = max(src & RED_MASK, dst & RED_MASK) | + max(src & BLUE_MASK, dst & BLUE_MASK); + int gn = max(src & GREEN_MASK, dst & GREEN_MASK); + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK; } /** - * returns the absolute value of the difference of the input colors - * C = |A - B| + * Darkest + * O = MIN(D, S) */ - private static int blend_difference(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = (ar > br) ? (ar-br) : (br-ar); - int cg = (ag > bg) ? (ag-bg) : (bg-ag); - int cb = (ab > bb) ? (ab-bb) : (bb-ab); - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_darkest(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int rb = min(src & RED_MASK, dst & RED_MASK) | + min(src & BLUE_MASK, dst & BLUE_MASK); + int gn = min(src & GREEN_MASK, dst & GREEN_MASK); + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK; } /** - * Cousin of difference, algorithm used here is based on a Lingo version - * found here: http://www.mediamacros.com/item/item-1006687616/ - * (Not yet verified to be correct). + * Difference + * O = ABS(D - S) */ - private static int blend_exclusion(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = ar + br - ((ar * br) >> 7); - int cg = ag + bg - ((ag * bg) >> 7); - int cb = ab + bb - ((ab * bb) >> 7); - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_difference(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int r = (dst & RED_MASK) - (src & RED_MASK); + int b = (dst & BLUE_MASK) - (src & BLUE_MASK); + int g = (dst & GREEN_MASK) - (src & GREEN_MASK); + + int rb = (r < 0 ? -r : r) | + (b < 0 ? -b : b); + int gn = (g < 0 ? -g : g); + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK; } /** - * returns the product of the input colors - * C = A * B + * Exclusion + * O = (1 - S)D + S(1 - D) + * O = D + S - 2DS + */ + private static int blend_exclusion(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int d_rb = dst & RB_MASK; + int d_gn = dst & GN_MASK; + + int s_gn = src & GN_MASK; + + int f_r = (dst & RED_MASK) >> 16; + int f_b = (dst & BLUE_MASK); + + int rb_sub = + ((src & RED_MASK) * (f_r + (f_r >= 0x7F ? 1 : 0)) | + (src & BLUE_MASK) * (f_b + (f_b >= 0x7F ? 1 : 0))) + >>> 7 & 0x01FF01FF; + int gn_sub = s_gn * (d_gn + (d_gn >= 0x7F00 ? 0x100 : 0)) + >>> 15 & 0x0001FF00; + + return min((dst >>> 24) + a, 0xFF) << 24 | + (d_rb * d_a + (d_rb + (src & RB_MASK) - rb_sub) * s_a) >>> 8 & RB_MASK | + (d_gn * d_a + (d_gn + s_gn - gn_sub) * s_a) >>> 8 & GN_MASK; + } + + + /* + * Multiply + * O = DS */ - private static int blend_multiply(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = (ar * br) >> 8; - int cg = (ag * bg) >> 8; - int cb = (ab * bb) >> 8; - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_multiply(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int d_gn = dst & GN_MASK; + + int f_r = (dst & RED_MASK) >> 16; + int f_b = (dst & BLUE_MASK); + + int rb = + ((src & RED_MASK) * (f_r + 1) | + (src & BLUE_MASK) * (f_b + 1)) + >>> 8 & RB_MASK; + int gn = + (src & GREEN_MASK) * (d_gn + 0x100) + >>> 16 & GN_MASK; + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK | + (d_gn * d_a + gn * s_a) >>> 8 & GN_MASK; } /** - * returns the inverse of the product of the inverses of the input colors - * (the inverse of multiply). C = 1 - (1-A) * (1-B) + * Screen + * O = 1 - (1 - D)(1 - S) + * O = D + S - DS */ - private static int blend_screen(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = 255 - (((255 - ar) * (255 - br)) >> 8); - int cg = 255 - (((255 - ag) * (255 - bg)) >> 8); - int cb = 255 - (((255 - ab) * (255 - bb)) >> 8); - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_screen(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int d_rb = dst & RB_MASK; + int d_gn = dst & GN_MASK; + + int s_gn = src & GN_MASK; + + int f_r = (dst & RED_MASK) >> 16; + int f_b = (dst & BLUE_MASK); + + int rb_sub = + ((src & RED_MASK) * (f_r + 1) | + (src & BLUE_MASK) * (f_b + 1)) + >>> 8 & RB_MASK; + int gn_sub = s_gn * (d_gn + 0x100) + >>> 16 & GN_MASK; + + return min((dst >>> 24) + a, 0xFF) << 24 | + (d_rb * d_a + (d_rb + (src & RB_MASK) - rb_sub) * s_a) >>> 8 & RB_MASK | + (d_gn * d_a + (d_gn + s_gn - gn_sub) * s_a) >>> 8 & GN_MASK; } /** - * returns either multiply or screen for darker or lighter values of A - * (the inverse of hard light) - * C = - * A < 0.5 : 2 * A * B - * A >=0.5 : 1 - (2 * (255-A) * (255-B)) + * Overlay + * O = 2 * MULTIPLY(D, S) = 2DS for D < 0.5 + * O = 2 * SCREEN(D, S) - 1 = 2(S + D - DS) - 1 otherwise */ - private static int blend_overlay(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = (ar < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); - int cg = (ag < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); - int cb = (ab < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_overlay(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int d_r = dst & RED_MASK; + int d_g = dst & GREEN_MASK; + int d_b = dst & BLUE_MASK; + + int s_r = src & RED_MASK; + int s_g = src & GREEN_MASK; + int s_b = src & BLUE_MASK; + + int r = (d_r < 0x800000) ? + d_r * ((s_r >>> 16) + 1) >>> 7 : + 0xFF0000 - ((0x100 - (s_r >>> 16)) * (RED_MASK - d_r) >>> 7); + int g = (d_g < 0x8000) ? + d_g * (s_g + 0x100) >>> 15 : + (0xFF00 - ((0x10000 - s_g) * (GREEN_MASK - d_g) >>> 15)); + int b = (d_b < 0x80) ? + d_b * (s_b + 1) >>> 7 : + (0xFF00 - ((0x100 - s_b) * (BLUE_MASK - d_b) << 1)) >>> 8; + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + ((r | b) & RB_MASK) * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + (g & GN_MASK) * s_a) >>> 8 & GN_MASK; } /** - * returns either multiply or screen for darker or lighter values of B - * (the inverse of overlay) - * C = - * B < 0.5 : 2 * A * B - * B >=0.5 : 1 - (2 * (255-A) * (255-B)) + * Hard Light + * O = OVERLAY(S, D) + * + * O = 2 * MULTIPLY(D, S) = 2DS for S < 0.5 + * O = 2 * SCREEN(D, S) - 1 = 2(S + D - DS) - 1 otherwise */ - private static int blend_hard_light(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = (br < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7)); - int cg = (bg < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7)); - int cb = (bb < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7)); - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_hard_light(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int d_r = dst & RED_MASK; + int d_g = dst & GREEN_MASK; + int d_b = dst & BLUE_MASK; + + int s_r = src & RED_MASK; + int s_g = src & GREEN_MASK; + int s_b = src & BLUE_MASK; + + int r = (s_r < 0x800000) ? + s_r * ((d_r >>> 16) + 1) >>> 7 : + 0xFF0000 - ((0x100 - (d_r >>> 16)) * (RED_MASK - s_r) >>> 7); + int g = (s_g < 0x8000) ? + s_g * (d_g + 0x100) >>> 15 : + (0xFF00 - ((0x10000 - d_g) * (GREEN_MASK - s_g) >>> 15)); + int b = (s_b < 0x80) ? + s_b * (d_b + 1) >>> 7 : + (0xFF00 - ((0x100 - d_b) * (BLUE_MASK - s_b) << 1)) >>> 8; + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + ((r | b) & RB_MASK) * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + (g & GN_MASK) * s_a) >>> 8 & GN_MASK; } /** - * returns the inverse multiply plus screen, which simplifies to - * C = 2AB + A^2 - 2A^2B + * Soft Light (Pegtop) + * O = (1 - D) * MULTIPLY(D, S) + D * SCREEN(D, S) + * O = (1 - D) * DS + D * (1 - (1 - D)(1 - S)) + * O = 2DS + DD - 2DDS */ - private static int blend_soft_light(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = ((ar*br)>>7) + ((ar*ar)>>8) - ((ar*ar*br)>>15); - int cg = ((ag*bg)>>7) + ((ag*ag)>>8) - ((ag*ag*bg)>>15); - int cb = ((ab*bb)>>7) + ((ab*ab)>>8) - ((ab*ab*bb)>>15); - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_soft_light(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int d_r = dst & RED_MASK; + int d_g = dst & GREEN_MASK; + int d_b = dst & BLUE_MASK; + + int s_r1 = src & RED_MASK >> 16; + int s_g1 = src & GREEN_MASK >> 8; + int s_b1 = src & BLUE_MASK; + + int d_r1 = (d_r >> 16) + (s_r1 < 7F ? 1 : 0); + int d_g1 = (d_g >> 8) + (s_g1 < 7F ? 1 : 0); + int d_b1 = d_b + (s_b1 < 7F ? 1 : 0); + + int r = (s_r1 * d_r >> 7) + 0xFF * d_r1 * (d_r1 + 1) - + ((s_r1 * d_r1 * d_r1) << 1) & RED_MASK; + int g = (s_g1 * d_g << 1) + 0xFF * d_g1 * (d_g1 + 1) - + ((s_g1 * d_g1 * d_g1) << 1) >>> 8 & GREEN_MASK; + int b = (s_b1 * d_b << 9) + 0xFF * d_b1 * (d_b1 + 1) - + ((s_b1 * d_b1 * d_b1) << 1) >>> 16; + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + (r | b) * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + g * s_a) >>> 8 & GN_MASK; } /** - * Returns the first (underlay) color divided by the inverse of - * the second (overlay) color. C = A / (255-B) + * Dodge + * O = D / (1 - S) */ - private static int blend_dodge(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = (br==255) ? 255 : peg((ar << 8) / (255 - br)); // division requires pre-peg()-ing - int cg = (bg==255) ? 255 : peg((ag << 8) / (255 - bg)); // " - int cb = (bb==255) ? 255 : peg((ab << 8) / (255 - bb)); // " - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_dodge(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int r = (dst & RED_MASK) / (256 - ((src & RED_MASK) >> 16)); + int g = ((dst & GREEN_MASK) << 8) / (256 - ((src & GREEN_MASK) >> 8)); + int b = ((dst & BLUE_MASK) << 8) / (256 - (src & BLUE_MASK)); + + int rb = + (r > 0xFF00 ? 0xFF0000 : ((r << 8) & RED_MASK)) | + (b > 0x00FF ? 0x0000FF : b); + int gn = + (g > 0xFF00 ? 0x00FF00 : (g & GREEN_MASK)); + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK; } /** - * returns the inverse of the inverse of the first (underlay) color - * divided by the second (overlay) color. C = 255 - (255-A) / B + * Burn + * O = 1 - (1 - A) / B */ - private static int blend_burn(int a, int b) { - // setup (this portion will always be the same) - int f = (b & ALPHA_MASK) >>> 24; - int ar = (a & RED_MASK) >> 16; - int ag = (a & GREEN_MASK) >> 8; - int ab = (a & BLUE_MASK); - int br = (b & RED_MASK) >> 16; - int bg = (b & GREEN_MASK) >> 8; - int bb = (b & BLUE_MASK); - // formula: - int cr = (br==0) ? 0 : 255 - peg(((255 - ar) << 8) / br); // division requires pre-peg()-ing - int cg = (bg==0) ? 0 : 255 - peg(((255 - ag) << 8) / bg); // " - int cb = (bb==0) ? 0 : 255 - peg(((255 - ab) << 8) / bb); // " - // alpha blend (this portion will always be the same) - return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | - (peg(ar + (((cr - ar) * f) >> 8)) << 16) | - (peg(ag + (((cg - ag) * f) >> 8)) << 8) | - (peg(ab + (((cb - ab) * f) >> 8)) ) ); + private static int blend_burn(int dst, int src) { + int a = src >>> 24; + + int s_a = a + (a >= 0x7F ? 1 : 0); + int d_a = 0x100 - s_a; + + int r = ((0xFF0000 - (dst & RED_MASK))) / (1 + (src & RED_MASK >> 16)); + int g = ((0x00FF00 - (dst & GREEN_MASK)) << 8) / (1 + (src & GREEN_MASK >> 8)); + int b = ((0x0000FF - (dst & BLUE_MASK)) << 8) / (1 + (src & BLUE_MASK)); + + int rb = RB_MASK - + (r > 0xFF00 ? 0xFF0000 : ((r << 8) & RED_MASK)) - + (b > 0x00FF ? 0x0000FF : b); + int gn = GN_MASK - + (g > 0xFF00 ? 0x00FF00 : (g & GREEN_MASK)); + + return min((dst >>> 24) + a, 0xFF) << 24 | + ((dst & RB_MASK) * d_a + rb * s_a) >>> 8 & RB_MASK | + ((dst & GN_MASK) * d_a + gn * s_a) >>> 8 & GN_MASK; } diff --git a/core/src/processing/core/PMatrix.java b/libs/processing-core/src/main/java/processing/core/PMatrix.java similarity index 71% rename from core/src/processing/core/PMatrix.java rename to libs/processing-core/src/main/java/processing/core/PMatrix.java index 283039629..1c6fabc3f 100644 --- a/core/src/processing/core/PMatrix.java +++ b/libs/processing-core/src/main/java/processing/core/PMatrix.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2005-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2005-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -24,26 +25,26 @@ public interface PMatrix { - + public void reset(); - + /** * Returns a copy of this PMatrix. */ - public PMatrix get(); + public PMatrix get(); /** * Copies the matrix contents into a float array. * If target is null (or not the correct size), a new array will be created. */ public float[] get(float[] target); - - + + public void set(PMatrix src); public void set(float[] source); - public void set(float m00, float m01, float m02, + public void set(float m00, float m01, float m02, float m10, float m11, float m12); public void set(float m00, float m01, float m02, float m03, @@ -51,9 +52,9 @@ public void set(float m00, float m01, float m02, float m03, float m20, float m21, float m22, float m23, float m30, float m31, float m32, float m33); - + public void translate(float tx, float ty); - + public void translate(float tx, float ty, float tz); public void rotate(float angle); @@ -71,12 +72,12 @@ public void set(float m00, float m01, float m02, float m03, public void scale(float sx, float sy); public void scale(float x, float y, float z); - + public void shearX(float angle); - + public void shearY(float angle); - /** + /** * Multiply this matrix by another. */ public void apply(PMatrix source); @@ -85,7 +86,7 @@ public void set(float m00, float m01, float m02, float m03, public void apply(PMatrix3D source); - public void apply(float n00, float n01, float n02, + public void apply(float n00, float n01, float n02, float n10, float n11, float n12); public void apply(float n00, float n01, float n02, float n03, @@ -93,55 +94,75 @@ public void apply(float n00, float n01, float n02, float n03, float n20, float n21, float n22, float n23, float n30, float n31, float n32, float n33); + /** + * Apply another matrix to the left of this one. + */ + public void preApply(PMatrix left); + /** * Apply another matrix to the left of this one. */ public void preApply(PMatrix2D left); + /** + * Apply another matrix to the left of this one. 3D only. + */ public void preApply(PMatrix3D left); - public void preApply(float n00, float n01, float n02, + /** + * Apply another matrix to the left of this one. + */ + public void preApply(float n00, float n01, float n02, float n10, float n11, float n12); + /** + * Apply another matrix to the left of this one. 3D only. + */ public void preApply(float n00, float n01, float n02, float n03, float n10, float n11, float n12, float n13, float n20, float n21, float n22, float n23, float n30, float n31, float n32, float n33); - - /** - * Multiply a PVector by this matrix. + + /** + * Multiply source by this matrix, and return the result. + * The result will be stored in target if target is non-null, and target + * will then be the matrix returned. This improves performance if you reuse + * target, so it's recommended if you call this many times in draw(). */ public PVector mult(PVector source, PVector target); - - - /** - * Multiply a multi-element vector against this matrix. + + + /** + * Multiply a multi-element vector against this matrix. + * Supplying and recycling a target array improves performance, so it's + * recommended if you call this many times in draw(). */ public float[] mult(float[] source, float[] target); - - + + // public float multX(float x, float y); // public float multY(float x, float y); - + // public float multX(float x, float y, float z); // public float multY(float x, float y, float z); -// public float multZ(float x, float y, float z); - - +// public float multZ(float x, float y, float z); + + /** - * Transpose this matrix. + * Transpose this matrix; rows become columns and columns rows. */ public void transpose(); - + /** - * Invert this matrix. + * Invert this matrix. Will not necessarily succeed, because some matrices + * map more than one point to the same image point, and so are irreversible. * @return true if successful */ public boolean invert(); - - + + /** * @return the determinant of the matrix */ diff --git a/core/src/processing/core/PMatrix2D.java b/libs/processing-core/src/main/java/processing/core/PMatrix2D.java similarity index 96% rename from core/src/processing/core/PMatrix2D.java rename to libs/processing-core/src/main/java/processing/core/PMatrix2D.java index 585a9cf84..8d13979b7 100644 --- a/core/src/processing/core/PMatrix2D.java +++ b/libs/processing-core/src/main/java/processing/core/PMatrix2D.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2005-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2005-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -243,6 +244,18 @@ public void apply(float n00, float n01, float n02, float n03, } + /** + * Apply another matrix to the left of this one. + */ + public void preApply(PMatrix source) { + if (source instanceof PMatrix2D) { + preApply((PMatrix2D) source); + } else if (source instanceof PMatrix3D) { + preApply((PMatrix3D) source); + } + } + + /** * Apply another matrix to the left of this one. */ @@ -410,11 +423,11 @@ public void print() { ////////////////////////////////////////////////////////////// - // TODO these need to be added as regular API, but the naming and + // TODO these need to be added as regular API, but the naming and // implementation needs to be improved first. (e.g. actually keeping track // of whether the matrix is in fact identity internally.) - + protected boolean isIdentity() { return ((m00 == 1) && (m01 == 0) && (m02 == 0) && (m10 == 0) && (m11 == 1) && (m12 == 0)); @@ -424,14 +437,14 @@ protected boolean isIdentity() { // TODO make this more efficient, or move into PMatrix2D protected boolean isWarped() { // was &&, but changed so shearX and shearY will work - return ((m00 != 1) || (m01 != 0) || + return ((m00 != 1) || (m01 != 0) || (m10 != 0) || (m11 != 1)); } ////////////////////////////////////////////////////////////// - + private final float max(float a, float b) { return (a > b) ? a : b; } @@ -447,7 +460,7 @@ private final float sin(float angle) { private final float cos(float angle) { return (float)Math.cos(angle); } - + private final float tan(float angle) { return (float)Math.tan(angle); } diff --git a/core/src/processing/core/PMatrix3D.java b/libs/processing-core/src/main/java/processing/core/PMatrix3D.java similarity index 98% rename from core/src/processing/core/PMatrix3D.java rename to libs/processing-core/src/main/java/processing/core/PMatrix3D.java index deeb23b45..9167af1f0 100644 --- a/core/src/processing/core/PMatrix3D.java +++ b/libs/processing-core/src/main/java/processing/core/PMatrix3D.java @@ -3,6 +3,7 @@ /* Part of the Processing project - http://processing.org + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2005-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or @@ -358,6 +359,18 @@ public void apply(float n00, float n01, float n02, float n03, } + /** + * Apply another matrix to the left of this one. + */ + public void preApply(PMatrix source) { + if (source instanceof PMatrix2D) { + preApply((PMatrix2D) source); + } else if (source instanceof PMatrix3D) { + preApply((PMatrix3D) source); + } + } + + public void preApply(PMatrix2D left) { preApply(left.m00, left.m01, 0, left.m02, left.m10, left.m11, 0, left.m12, diff --git a/core/src/processing/core/PShape.java b/libs/processing-core/src/main/java/processing/core/PShape.java similarity index 80% rename from core/src/processing/core/PShape.java rename to libs/processing-core/src/main/java/processing/core/PShape.java index 85e626e9c..a609a9e4e 100644 --- a/core/src/processing/core/PShape.java +++ b/libs/processing-core/src/main/java/processing/core/PShape.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2006-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2006-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -22,9 +23,11 @@ package processing.core; -import java.util.HashMap; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; -import processing.core.PApplet; +import java.util.HashMap; +import java.util.Map; /** @@ -78,17 +81,21 @@ */ public class PShape implements PConstants { protected String name; - protected HashMap nameTable; + protected Map nameTable; // /** Generic, only draws its child objects. */ // static public final int GROUP = 0; - // GROUP now inherited from PConstants + // GROUP now inherited from PConstants, and is still zero + + // These constants were updated in 3.0b6 so that they could be distinguished + // from others in PConstants and improve how some typos were handled. + // https://github.com/processing/processing/issues/3776 /** A line, ellipse, arc, image, etc. */ - static public final int PRIMITIVE = 1; + static public final int PRIMITIVE = 101; /** A series of vertex, curveVertex, and bezierVertex calls. */ - static public final int PATH = 2; + static public final int PATH = 102; /** Collections of vertices created with beginShape(). */ - static public final int GEOMETRY = 3; + static public final int GEOMETRY = 103; /** The shape type, one of GROUP, PRIMITIVE, PATH, or GEOMETRY. */ protected int family; @@ -101,6 +108,7 @@ public class PShape implements PConstants { /** Texture or image data associated with this shape. */ protected PImage image; + protected String imagePath = null; public static final String OUTSIDE_BEGIN_END_ERROR = "%1$s can only be called between beginShape() and endShape()"; @@ -108,11 +116,18 @@ public class PShape implements PConstants { public static final String INSIDE_BEGIN_END_ERROR = "%1$s can only be called outside beginShape() and endShape()"; - // boundary box of this shape - //protected float x; - //protected float y; - //protected float width; - //protected float height; + public static final String NO_SUCH_VERTEX_ERROR = + "%1$s vertex index does not exist"; + + static public final String NO_VERTICES_ERROR = + "getVertexCount() only works with PATH or GEOMETRY shapes"; + + public static final String NOT_A_SIMPLE_VERTEX = + "%1$s can not be called on quadratic or bezier vertices"; + + static public final String PER_VERTEX_UNSUPPORTED = + "This renderer does not support %1$s for individual vertices"; + /** * ( begin auto-generated from PShape_width.xml ) * @@ -140,6 +155,8 @@ public class PShape implements PConstants { public float depth; + PGraphics g; + // set to false if the object is hidden in the layers palette protected boolean visible = true; @@ -227,6 +244,9 @@ public class PShape implements PConstants { /** True if contains 3D data */ protected boolean is3D = false; + protected boolean perVertexStyles = false; + + // should this be called vertices (consistent with PGraphics internals) // or does that hurt flexibility? @@ -262,19 +282,98 @@ public class PShape implements PConstants { // public float px; // public float py; + + /** + * @nowebref + */ public PShape() { this.family = GROUP; } -/** - * @nowebref - */ + /** + * @nowebref + */ public PShape(int family) { this.family = family; } + /** + * @nowebref + */ + public PShape(PGraphics g, int family) { + this.g = g; + this.family = family; + + // Style parameters are retrieved from the current values in the renderer. + textureMode = g.textureMode; + + colorMode(g.colorMode, + g.colorModeX, g.colorModeY, g.colorModeZ, g.colorModeA); + + // Initial values for fill, stroke and tint colors are also imported from + // the renderer. This is particular relevant for primitive shapes, since is + // not possible to set their color separately when creating them, and their + // input vertices are actually generated at rendering time, by which the + // color configuration of the renderer might have changed. + fill = g.fill; + fillColor = g.fillColor; + + stroke = g.stroke; + strokeColor = g.strokeColor; + strokeWeight = g.strokeWeight; + strokeCap = g.strokeCap; + strokeJoin = g.strokeJoin; + + tint = g.tint; + tintColor = g.tintColor; + + setAmbient = g.setAmbient; + ambientColor = g.ambientColor; + specularColor = g.specularColor; + emissiveColor = g.emissiveColor; + shininess = g.shininess; + + sphereDetailU = g.sphereDetailU; + sphereDetailV = g.sphereDetailV; + +// bezierDetail = pg.bezierDetail; +// curveDetail = pg.curveDetail; +// curveTightness = pg.curveTightness; + + rectMode = g.rectMode; + ellipseMode = g.ellipseMode; + +// normalX = normalY = 0; +// normalZ = 1; +// +// normalMode = NORMAL_MODE_AUTO; + + // To make sure that the first vertex is marked as a break. + // Same behavior as in the immediate mode. +// breakShape = false; + + if (family == GROUP) { + // GROUP shapes are always marked as ended. +// shapeCreated = true; + // TODO why was this commented out? + } + } + + + public PShape(PGraphics g, int kind, float... params) { + this(g, PRIMITIVE); + setKind(kind); + setParams(params); + } + + + public void setFamily(int family) { + this.family = family; + } + + public void setKind(int kind) { this.kind = kind; } @@ -467,7 +566,7 @@ public boolean is3D() { } - public void is3D(boolean val) { + public void set3D(boolean val) { is3D = val; } @@ -514,10 +613,12 @@ public void noTexture() { image = null; } + // TODO unapproved protected void solid(boolean solid) { } + /** * @webref shape:vertex * @brief Starts a new contour @@ -544,7 +645,9 @@ public void beginContour() { protected void beginContourImpl() { - if (vertexCodes.length == vertexCodeCount) { + if (vertexCodes == null) { + vertexCodes = new int[10]; + } else if (vertexCodes.length == vertexCodeCount) { vertexCodes = PApplet.expand(vertexCodes); } vertexCodes[vertexCodeCount++] = BREAK; @@ -609,6 +712,7 @@ public void vertex(float x, float y, float u, float v) { public void vertex(float x, float y, float z) { + vertex(x, y); // maybe? maybe not? } @@ -620,6 +724,34 @@ public void normal(float nx, float ny, float nz) { } + public void attribPosition(String name, float x, float y, float z) { + } + + public void attribNormal(String name, float nx, float ny, float nz) { + } + + + public void attribColor(String name, int color) { + } + + + public void attrib(String name, float... values) { + } + + + public void attrib(String name, int... values) { + } + + + public void attrib(String name, boolean... values) { + } + + + /** + * @webref pshape:method + * @brief Starts the creation of a new PShape + * @see PApplet#endShape() + */ public void beginShape() { beginShape(POLYGON); } @@ -630,11 +762,10 @@ public void beginShape(int kind) { openShape = true; } - /** * @webref pshape:method * @brief Finishes the creation of a new PShape - * @see PApplet#createShape() + * @see PApplet#beginShape() */ public void endShape() { endShape(OPEN); @@ -652,6 +783,9 @@ public void endShape(int mode) { return; } + close = (mode==CLOSE); + + // this is the state of the shape openShape = false; } @@ -767,6 +901,11 @@ public void fill(float gray, float alpha) { colorCalc(gray, alpha); fillColor = calcColor; + if (!setAmbient) { + ambient(fillColor); + setAmbient = false; + } + if (!setAmbient) { ambientColor = fillColor; } @@ -1152,11 +1291,6 @@ public void bezierVertex(float x2, float y2, float z2, public void quadraticVertex(float cx, float cy, float x3, float y3) { - } - - - public void quadraticVertex(float cx, float cy, float cz, - float x3, float y3, float z3) { if (vertices == null) { vertices = new float[10][]; } else if (vertexCount + 1 >= vertices.length) { @@ -1180,29 +1314,31 @@ public void quadraticVertex(float cx, float cy, float cz, } + public void quadraticVertex(float cx, float cy, float cz, + float x3, float y3, float z3) { + } + + /////////////////////////////////////////////////////////// // // Catmull-Rom curves - public void curveDetail(int detail) { } - public void curveTightness(float tightness) { } - public void curveVertex(float x, float y) { } - public void curveVertex(float x, float y, float z) { } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1358,12 +1494,10 @@ static protected void copyGeometry(PShape src, PShape dest) { for (int i = 0; i < src.vertexCount; i++) { float[] vert = src.vertices[i]; - - - dest.fill((int)(vert[PGraphics.R] * 255) << 24 | - (int)(vert[PGraphics.G] * 255) << 16 | - (int)(vert[PGraphics.B] * 255) << 8 | - (int)(vert[PGraphics.A] * 255)); + dest.fill((int)(vert[PGraphics.A] * 255) << 24 | + (int)(vert[PGraphics.R] * 255) << 16 | + (int)(vert[PGraphics.G] * 255) << 8 | + (int)(vert[PGraphics.B] * 255)); // Do we need to copy these as well? // dest.ambient(vert[PGraphics.AR] * 255, vert[PGraphics.AG] * 255, vert[PGraphics.AB] * 255); @@ -1417,6 +1551,9 @@ static protected void copyMatrix(PShape src, PShape dest) { // TODO unapproved static protected void copyStyles(PShape src, PShape dest) { + dest.ellipseMode = src.ellipseMode; + dest.rectMode = src.rectMode; + if (src.stroke) { dest.stroke = true; dest.strokeColor = src.strokeColor; @@ -1465,13 +1602,14 @@ public void draw(PGraphics g) { /** * Draws the SVG document. */ - public void drawImpl(PGraphics g) { - //System.out.println("drawing " + family); + protected void drawImpl(PGraphics g) { if (family == GROUP) { drawGroup(g); } else if (family == PRIMITIVE) { drawPrimitive(g); } else if (family == GEOMETRY) { + // Not same as path: `kind` matters. +// drawPath(g); drawGeometry(g); } else if (family == PATH) { drawPath(g); @@ -1511,21 +1649,54 @@ protected void drawPrimitive(PGraphics g) { params[6], params[7]); } else if (kind == RECT) { + + if (imagePath != null){ + loadImage(g); + } if (image != null) { + int oldMode = g.imageMode; g.imageMode(CORNER); g.image(image, params[0], params[1], params[2], params[3]); + g.imageMode(oldMode); } else { - g.rectMode(CORNER); - g.rect(params[0], params[1], params[2], params[3]); + int oldMode = g.rectMode; + g.rectMode(rectMode); + if (params.length == 4) { + g.rect(params[0], params[1], + params[2], params[3]); + } else if (params.length == 5) { + g.rect(params[0], params[1], + params[2], params[3], + params[4]); + } else if (params.length == 8) { + g.rect(params[0], params[1], + params[2], params[3], + params[4], params[5], + params[6], params[7]); + } + g.rectMode(oldMode); } - } else if (kind == ELLIPSE) { - g.ellipseMode(CORNER); - g.ellipse(params[0], params[1], params[2], params[3]); + int oldMode = g.ellipseMode; + g.ellipseMode(ellipseMode); + g.ellipse(params[0], params[1], + params[2], params[3]); + g.ellipseMode(oldMode); } else if (kind == ARC) { - g.ellipseMode(CORNER); - g.arc(params[0], params[1], params[2], params[3], params[4], params[5]); + int oldMode = g.ellipseMode; + g.ellipseMode(ellipseMode); + if (params.length == 6) { + g.arc(params[0], params[1], + params[2], params[3], + params[4], params[5]); + } else if (params.length == 7) { + g.arc(params[0], params[1], + params[2], params[3], + params[4], params[5], + (int) params[6]); + } + g.ellipseMode(oldMode); } else if (kind == BOX) { if (params.length == 1) { @@ -1557,7 +1728,7 @@ protected void drawGeometry(PGraphics g) { } } } - g.endShape(); + g.endShape(close ? CLOSE : OPEN); } @@ -1615,7 +1786,6 @@ protected void drawPath(PGraphics g) { } */ - protected void drawPath(PGraphics g) { // Paths might be empty (go figure) // http://dev.processing.org/bugs/show_bug.cgi?id=982 @@ -1718,6 +1888,90 @@ protected void drawPath(PGraphics g) { } + private void loadImage(PGraphics g){ + if (this.imagePath.startsWith("data:image")){ + loadBase64Image(); + } + + if (this.imagePath.startsWith("file://")){ + loadFileSystemImage(g); + } + this.imagePath = null; + } + + private void loadFileSystemImage(PGraphics g){ + imagePath = imagePath.substring(7); + PImage loadedImage = g.parent.loadImage(imagePath); + if (loadedImage == null){ + System.err.println("Error loading image file: " + imagePath); + } else{ + setTexture(loadedImage); + } + } + + private void loadBase64Image(){ + String[] parts = this.imagePath.split(";base64,"); + String extension = parts[0].substring(11); + String encodedData = parts[1]; + +// byte[] decodedBytes = DatatypeConverter.parseBase64Binary(encodedData); + byte[] decodedBytes = parseHexBinary(encodedData); + + if(decodedBytes == null){ + System.err.println("Decode Error on image: " + imagePath.substring(0, 20)); + return; + } + +// Image awtImage = new ImageIcon(decodedBytes).getImage(); + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inMutable = true; + Bitmap bmp = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.length, options); + +// if (awtImage instanceof BufferedImage) { +// BufferedImage buffImage = (BufferedImage) awtImage; +// int space = buffImage.getColorModel().getColorSpace().getType(); +// if (space == ColorSpace.TYPE_CMYK) { +// return; +// } +// } + + PImage loadedImage = new PImage(bmp); + if (loadedImage.width == -1) { + // error... + } + + // if it's a .gif image, test to see if it has transparency + if (extension.equals("gif") || extension.equals("png") || + extension.equals("unknown")) { + loadedImage.checkAlpha(); + } + + setTexture(loadedImage); + } + + + // Replacement for DatatypeConverter + // https://github.com/hierynomus/sshj/issues/366#issue-261511648 + private static byte[] parseHexBinary(String s) + throws IllegalArgumentException { + if (s == null) { + return new byte[0]; + } + s = s.trim(); + int length = s.length(); + + if (length % 2 != 0) { + throw new IllegalArgumentException("Invalid hex string length."); + } + + byte[] result = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + result[i/2] = (byte) Integer.parseInt(s.substring(i, i + 2), 16); + } + return result; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -1725,12 +1979,26 @@ public PShape getParent() { return parent; } + /** + * @webref + * @brief Returns the number of children + */ public int getChildCount() { return childCount; } + /** Resize the children[] array to be in line with childCount */ + protected void crop() { + // https://github.com/processing/processing/issues/3347 + if (children.length != childCount) { + children = (PShape[]) PApplet.subset(children, 0, childCount); + } + } + + public PShape[] getChildren() { + crop(); return children; } @@ -1749,6 +2017,7 @@ public PShape[] getChildren() { * @see PShape#addChild(PShape) */ public PShape getChild(int index) { + crop(); return children[index]; } @@ -1863,7 +2132,7 @@ public void addName(String nom, PShape shape) { parent.addName(nom, shape); } else { if (nameTable == null) { - nameTable = new HashMap(); + nameTable = new HashMap<>(); } nameTable.put(nom, shape); } @@ -1963,6 +2232,9 @@ protected void setPath(int vcount, float[][] verts, int ccount, int[] codes) { * @see PShape#setVertex(int, float, float) */ public int getVertexCount() { + if (family == GROUP || family == PRIMITIVE) { + PGraphics.showWarning(NO_VERTICES_ERROR); + } return vertexCount; } @@ -1978,6 +2250,7 @@ public PVector getVertex(int index) { return getVertex(index, null); } + /** * @param vec PVector to assign the data to */ @@ -2011,6 +2284,7 @@ public float getVertexZ(int index) { return vertices[index][Z]; } + /** * @webref pshape:method * @brief Sets the vertex at the index position @@ -2030,6 +2304,7 @@ public void setVertex(int index, float x, float y) { vertices[index][Y] = y; } + /** * @param z the z value for the vertex */ @@ -2044,6 +2319,7 @@ public void setVertex(int index, float x, float y, float z) { vertices[index][Z] = z; } + /** * @param vec the PVector to define the x, y, z coordinates */ @@ -2055,7 +2331,12 @@ public void setVertex(int index, PVector vec) { vertices[index][X] = vec.x; vertices[index][Y] = vec.y; - vertices[index][Z] = vec.z; + + if (vertices[index].length > 2) { + vertices[index][Z] = vec.z; + } else if (vec.z != 0 && vec.z == vec.z) { + throw new IllegalArgumentException("Cannot set a z-coordinate on a 2D shape"); + } } @@ -2102,6 +2383,19 @@ public void setNormal(int index, float nx, float ny, float nz) { } + + public void setAttrib(String name, int index, float... values) { + } + + + public void setAttrib(String name, int index, int... values) { + } + + + public void setAttrib(String name, int index, boolean... values) { + } + + public float getTextureU(int index) { return vertices[index][PGraphics.U]; } @@ -2118,6 +2412,14 @@ public void setTextureUV(int index, float u, float v) { return; } + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setTextureUV()"); + return; + } + + vertices[index][PGraphics.U] = u; vertices[index][PGraphics.V] = v; } @@ -2144,6 +2446,13 @@ public void setTexture(PImage tex) { public int getFill(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getFill()"); + return fillColor; + } + if (image == null) { int a = (int) (vertices[index][PGraphics.A] * 255); int r = (int) (vertices[index][PGraphics.R] * 255); @@ -2155,7 +2464,9 @@ public int getFill(int index) { } } - + /** + * @nowebref + */ public void setFill(boolean fill) { if (openShape) { PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setFill()"); @@ -2166,24 +2477,59 @@ public void setFill(boolean fill) { } + /** + * ( begin auto-generated from PShape_setFill.xml ) + * + * The setFill() method defines the fill color of a PShape. + * This method is used after shapes are created or when a shape is defined explicitly + * (e.g. createShape(RECT, 20, 20, 80, 80)) as shown in the above example. + * When a shape is created with beginShape() and endShape(), its + * attributes may be changed with fill() and stroke() within + * beginShape() and endShape(). However, after the shape is + * created, only the setFill() method can define a new fill value for + * the PShape. + * + * ( end auto-generated ) + * + * @webref + * @param fill + * @brief Set the fill value + */ public void setFill(int fill) { if (openShape) { PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setFill()"); return; } - for (int i = 0; i < vertices.length; i++) { - setFill(i, fill); + this.fillColor = fill; + + if (vertices != null && perVertexStyles) { + for (int i = 0; i < vertexCount; i++) { + setFill(i, fill); + } } } - + /** + * @nowebref + */ public void setFill(int index, int fill) { if (openShape) { PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setFill()"); return; } + if (!perVertexStyles) { + PGraphics.showWarning(PER_VERTEX_UNSUPPORTED, "setFill()"); + return; + } + + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getFill()"); + return; + } + if (image == null) { vertices[index][PGraphics.A] = ((fill >> 24) & 0xFF) / 255.0f; vertices[index][PGraphics.R] = ((fill >> 16) & 0xFF) / 255.0f; @@ -2194,6 +2540,12 @@ public void setFill(int index, int fill) { public int getTint(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getTint()"); + return this.tintColor; + } + if (image != null) { int a = (int) (vertices[index][PGraphics.A] * 255); int r = (int) (vertices[index][PGraphics.R] * 255); @@ -2222,8 +2574,12 @@ public void setTint(int fill) { return; } - for (int i = 0; i < vertices.length; i++) { - setFill(i, fill); + tintColor = fill; + + if (vertices != null) { + for (int i = 0; i < vertices.length; i++) { + setFill(i, fill); + } } } @@ -2234,6 +2590,13 @@ public void setTint(int index, int tint) { return; } + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setTint()"); + return; + } + if (image != null) { vertices[index][PGraphics.A] = ((tint >> 24) & 0xFF) / 255.0f; vertices[index][PGraphics.R] = ((tint >> 16) & 0xFF) / 255.0f; @@ -2244,6 +2607,13 @@ public void setTint(int index, int tint) { public int getStroke(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getStroke()"); + return strokeColor; + } + int a = (int) (vertices[index][PGraphics.SA] * 255); int r = (int) (vertices[index][PGraphics.SR] * 255); int g = (int) (vertices[index][PGraphics.SG] * 255); @@ -2252,6 +2622,9 @@ public int getStroke(int index) { } + /** + * @nowebref + */ public void setStroke(boolean stroke) { if (openShape) { PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setStroke()"); @@ -2262,24 +2635,60 @@ public void setStroke(boolean stroke) { } + /** + * ( begin auto-generated from PShape_setStroke.xml ) + * + * The setStroke() method defines the outline color of a PShape. + * This method is used after shapes are created or when a shape is defined + * explicitly (e.g. createShape(RECT, 20, 20, 80, 80)) as shown in + * the above example. When a shape is created with beginShape() and + * endShape(), its attributes may be changed with fill() and + * stroke() within beginShape() and endShape(). + * However, after the shape is created, only the setStroke() method + * can define a new stroke value for the PShape. + * + * ( end auto-generated ) + * + * @webref + * @param stroke + * @brief Set the stroke value + */ public void setStroke(int stroke) { if (openShape) { PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setStroke()"); return; } - for (int i = 0; i < vertices.length; i++) { - setStroke(i, stroke); + strokeColor = stroke; + + if (vertices != null && perVertexStyles) { + for (int i = 0; i < vertices.length; i++) { + setStroke(i, stroke); + } } } + /** + * @nowebref + */ public void setStroke(int index, int stroke) { if (openShape) { PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setStroke()"); return; } + if (!perVertexStyles) { + PGraphics.showWarning(PER_VERTEX_UNSUPPORTED, "setStroke()"); + return; + } + + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setStroke()"); + return; + } + vertices[index][PGraphics.SA] = ((stroke >> 24) & 0xFF) / 255.0f; vertices[index][PGraphics.SR] = ((stroke >> 16) & 0xFF) / 255.0f; vertices[index][PGraphics.SG] = ((stroke >> 8) & 0xFF) / 255.0f; @@ -2288,6 +2697,13 @@ public void setStroke(int index, int stroke) { public float getStrokeWeight(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getStrokeWeight()"); + return strokeWeight; + } + + return vertices[index][PGraphics.SW]; } @@ -2298,8 +2714,12 @@ public void setStrokeWeight(float weight) { return; } - for (int i = 0; i < vertices.length; i++) { - setStrokeWeight(i, weight); + strokeWeight = weight; + + if (vertices != null && perVertexStyles) { + for (int i = 0; i < vertexCount; i++) { + setStrokeWeight(i, weight); + } } } @@ -2310,6 +2730,17 @@ public void setStrokeWeight(int index, float weight) { return; } + if (!perVertexStyles) { + PGraphics.showWarning(PER_VERTEX_UNSUPPORTED, "setStrokeWeight()"); + return; + } + + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setStrokeWeight()"); + return; + } + vertices[index][PGraphics.SW] = weight; } @@ -2335,6 +2766,13 @@ public void setStrokeCap(int cap) { public int getAmbient(int index) { + + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getAmbient()"); + return ambientColor; + } + int r = (int) (vertices[index][PGraphics.AR] * 255); int g = (int) (vertices[index][PGraphics.AG] * 255); int b = (int) (vertices[index][PGraphics.AB] * 255); @@ -2348,8 +2786,12 @@ public void setAmbient(int ambient) { return; } - for (int i = 0; i < vertices.length; i++) { - setAmbient(i, ambient); + ambientColor = ambient; + + if (vertices != null) { + for (int i = 0; i < vertices.length; i++) { + setAmbient(i, ambient); + } } } @@ -2360,6 +2802,12 @@ public void setAmbient(int index, int ambient) { return; } + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setAmbient()"); + return; + } + vertices[index][PGraphics.AR] = ((ambient >> 16) & 0xFF) / 255.0f; vertices[index][PGraphics.AG] = ((ambient >> 8) & 0xFF) / 255.0f; vertices[index][PGraphics.AB] = ((ambient >> 0) & 0xFF) / 255.0f; @@ -2367,6 +2815,12 @@ public void setAmbient(int index, int ambient) { public int getSpecular(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getSpecular()"); + return specularColor; + } + int r = (int) (vertices[index][PGraphics.SPR] * 255); int g = (int) (vertices[index][PGraphics.SPG] * 255); int b = (int) (vertices[index][PGraphics.SPB] * 255); @@ -2380,8 +2834,12 @@ public void setSpecular(int specular) { return; } - for (int i = 0; i < vertices.length; i++) { - setSpecular(i, specular); + specularColor = specular; + + if (vertices != null) { + for (int i = 0; i < vertices.length; i++) { + setSpecular(i, specular); + } } } @@ -2392,6 +2850,12 @@ public void setSpecular(int index, int specular) { return; } + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setSpecular()"); + return; + } + vertices[index][PGraphics.SPR] = ((specular >> 16) & 0xFF) / 255.0f; vertices[index][PGraphics.SPG] = ((specular >> 8) & 0xFF) / 255.0f; vertices[index][PGraphics.SPB] = ((specular >> 0) & 0xFF) / 255.0f; @@ -2399,6 +2863,12 @@ public void setSpecular(int index, int specular) { public int getEmissive(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getEmissive()"); + return emissiveColor; + } + int r = (int) (vertices[index][PGraphics.ER] * 255); int g = (int) (vertices[index][PGraphics.EG] * 255); int b = (int) (vertices[index][PGraphics.EB] * 255); @@ -2412,8 +2882,12 @@ public void setEmissive(int emissive) { return; } - for (int i = 0; i < vertices.length; i++) { - setEmissive(i, emissive); + emissiveColor = emissive; + + if (vertices != null) { + for (int i = 0; i < vertices.length; i++) { + setEmissive(i, emissive); + } } } @@ -2424,6 +2898,13 @@ public void setEmissive(int index, int emissive) { return; } + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setEmissive()"); + return; + } + vertices[index][PGraphics.ER] = ((emissive >> 16) & 0xFF) / 255.0f; vertices[index][PGraphics.EG] = ((emissive >> 8) & 0xFF) / 255.0f; vertices[index][PGraphics.EB] = ((emissive >> 0) & 0xFF) / 255.0f; @@ -2431,6 +2912,13 @@ public void setEmissive(int index, int emissive) { public float getShininess(int index) { + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "getShininess()"); + return shininess; + } + return vertices[index][PGraphics.SHINE]; } @@ -2441,8 +2929,12 @@ public void setShininess(float shine) { return; } - for (int i = 0; i < vertices.length; i++) { - setShininess(i, shine); + shininess = shine; + + if (vertices != null) { + for (int i = 0; i < vertices.length; i++) { + setShininess(i, shine); + } } } @@ -2453,6 +2945,14 @@ public void setShininess(int index, float shine) { return; } + // make sure we allocated the vertices array and that vertex exists + if (vertices == null || + index >= vertices.length) { + PGraphics.showWarning(NO_SUCH_VERTEX_ERROR + " (" + index + ")", "setShininess()"); + return; + } + + vertices[index][PGraphics.SHINE] = shine; } @@ -2489,13 +2989,28 @@ public boolean isClosed() { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + /** + * Return true if this x, y coordinate is part of this shape. Only works + * with PATH shapes or GROUP shapes that contain other GROUPs or PATHs. + */ public boolean contains(float x, float y) { if (family == PATH) { + PVector p = new PVector(x, y); + if (matrix != null) { + // apply the inverse transformation matrix to the point coordinates + PMatrix inverseCoords = matrix.get(); + // TODO why is this called twice? [fry 190724] + // commit was https://github.com/processing/processing/commit/027fc7a4f8e8d0a435366eae754304eea282512a + inverseCoords.invert(); // maybe cache this? + inverseCoords.invert(); // maybe cache this? + inverseCoords.mult(new PVector(x, y), p); + } + + // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html boolean c = false; for (int i = 0, j = vertexCount-1; i < vertexCount; j = i++) { - if (((vertices[i][Y] > y) != (vertices[j][Y] > y)) && - (x < + if (((vertices[i][Y] > p.y) != (vertices[j][Y] > p.y)) && + (p.x < (vertices[j][X]-vertices[i][X]) * (y-vertices[i][Y]) / (vertices[j][1]-vertices[i][Y]) + @@ -2504,7 +3019,18 @@ public boolean contains(float x, float y) { } } return c; + + } else if (family == GROUP) { + // If this is a group, loop through children until we find one that + // contains the supplied coordinates. If a child does not support + // contains() throw a warning and continue. + for (int i = 0; i < childCount; i++) { + if (children[i].contains(x, y)) return true; + } + return false; + } else { + // https://github.com/processing/processing/issues/1280 throw new IllegalArgumentException("The contains() method is only implemented for paths."); } } @@ -2538,8 +3064,8 @@ public boolean contains(float x, float y) { * @webref pshape:method * @usage web_application * @brief Displaces the shape - * @param tx left/right translation - * @param ty up/down translation + * @param x left/right translation + * @param y up/down translation * @see PShape#rotate(float) * @see PShape#scale(float) * @see PShape#resetMatrix() @@ -2550,7 +3076,7 @@ public void translate(float x, float y) { } /** - * @param tz forward/back translation + * @param z forward/back translation */ public void translate(float x, float y, float z) { checkMatrix(3); @@ -3040,4 +3566,4 @@ protected void colorCalcARGB(int argb, float alpha) { calcAlpha = (calcAi != 255); } -} \ No newline at end of file +} diff --git a/core/src/processing/core/PShapeOBJ.java b/libs/processing-core/src/main/java/processing/core/PShapeOBJ.java similarity index 82% rename from core/src/processing/core/PShapeOBJ.java rename to libs/processing-core/src/main/java/processing/core/PShapeOBJ.java index 1f4bd50bb..470f3820a 100644 --- a/core/src/processing/core/PShapeOBJ.java +++ b/libs/processing-core/src/main/java/processing/core/PShapeOBJ.java @@ -1,10 +1,39 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + package processing.core; import java.io.BufferedReader; +import java.io.File; import java.util.ArrayList; import java.util.Hashtable; + /** + * This class is not part of the Processing API and should not be used + * directly. Instead, use loadShape() and methods like it, which will make + * use of this class. Using this class directly will cause your code to break + * when combined with future versions of Processing. + *

    * OBJ loading implemented using code from Saito's OBJLoader library: * http://code.google.com/p/saitoobjloader/ * and OBJReader from Ahmet Kizilay @@ -17,17 +46,21 @@ public class PShapeOBJ extends PShape { * Initializes a new OBJ Object with the given filename. */ public PShapeOBJ(PApplet parent, String filename) { - this(parent, parent.createReader(filename)); + this(parent, parent.createReader(filename), getBasePath(parent, filename)); } - public PShapeOBJ(PApplet parent, BufferedReader reader) { + this(parent, reader, ""); + } + + public PShapeOBJ(PApplet parent, BufferedReader reader, String basePath) { ArrayList faces = new ArrayList(); ArrayList materials = new ArrayList(); ArrayList coords = new ArrayList(); ArrayList normals = new ArrayList(); ArrayList texcoords = new ArrayList(); - parseOBJ(parent, reader, faces, materials, coords, normals, texcoords); + parseOBJ(parent, basePath, + reader, faces, materials, coords, normals, texcoords); // The OBJ geometry is stored with each face in a separate child shape. parent = null; @@ -142,7 +175,7 @@ protected void addChildren(ArrayList faces, } - static protected void parseOBJ(PApplet parent, + static protected void parseOBJ(PApplet parent, String path, BufferedReader reader, ArrayList faces, ArrayList materials, @@ -158,7 +191,7 @@ static protected void parseOBJ(PApplet parent, String line; String gname = "object"; while ((line = reader.readLine()) != null) { - // Parse the line. + // Parse the line. line = line.trim(); if (line.equals("") || line.indexOf('#') == 0) { // Empty line of comment, ignore line @@ -205,19 +238,23 @@ static protected void parseOBJ(PApplet parent, // uv, inverting v to take into account Processing's inverted Y axis // with respect to OpenGL. PVector tempv = new PVector(Float.valueOf(parts[1]).floatValue(), - 1 - Float.valueOf(parts[2]). - floatValue()); + 1 - Float.valueOf(parts[2]).floatValue()); texcoords.add(tempv); readvt = true; } else if (parts[0].equals("o")) { // Object name is ignored, for now. } else if (parts[0].equals("mtllib")) { if (parts[1] != null) { - BufferedReader mreader = parent.createReader(parts[1]); + String fn = parts[1]; + if (fn.indexOf(File.separator) == -1 && !path.equals("")) { + // Relative file name, adding the base path. + fn = path + File.separator + fn; + } + BufferedReader mreader = parent.createReader(fn); if (mreader != null) { - parseMTL(parent, mreader, materials, mtlTable); + parseMTL(parent, path, + mreader, materials, mtlTable); } - mreader.close(); } } else if (parts[0].equals("g")) { gname = 1 < parts.length ? parts[1] : ""; @@ -304,7 +341,7 @@ static protected void parseOBJ(PApplet parent, } - static protected void parseMTL(PApplet parent, + static protected void parseMTL(PApplet parent, String path, BufferedReader reader, ArrayList materials, Hashtable materialsHash) { @@ -321,11 +358,15 @@ static protected void parseMTL(PApplet parent, // Starting new material. String mtlname = parts[1]; currentMtl = new OBJMaterial(mtlname); - materialsHash.put(mtlname, new Integer(materials.size())); + materialsHash.put(mtlname, Integer.valueOf(materials.size())); materials.add(currentMtl); } else if (parts[0].equals("map_Kd") && parts.length > 1) { // Loading texture map. String texname = parts[1]; + if (texname.indexOf(File.separator) == -1 && !path.equals("")) { + // Relative file name, adding the base path. + texname = path + File.separator + texname; + } currentMtl.kdMap = parent.loadImage(texname); } else if (parts[0].equals("Ka") && parts.length > 3) { // The ambient color of the material @@ -343,7 +384,7 @@ static protected void parseMTL(PApplet parent, currentMtl.ks.y = Float.valueOf(parts[2]).floatValue(); currentMtl.ks.z = Float.valueOf(parts[3]).floatValue(); } else if ((parts[0].equals("d") || - parts[0].equals("Tr")) && parts.length > 1) { + parts[0].equals("Tr")) && parts.length > 1) { // Reading the alpha transparency. currentMtl.d = Float.valueOf(parts[1]).floatValue(); } else if (parts[0].equals("Ns") && parts.length > 1) { @@ -360,16 +401,16 @@ static protected void parseMTL(PApplet parent, protected static int rgbaValue(PVector color) { return 0xFF000000 | ((int)(color.x * 255) << 16) | - ((int)(color.y * 255) << 8) | - (int)(color.z * 255); + ((int)(color.y * 255) << 8) | + (int)(color.z * 255); } protected static int rgbaValue(PVector color, float alpha) { return ((int)(alpha * 255) << 24) | - ((int)(color.x * 255) << 16) | - ((int)(color.y * 255) << 8) | - (int)(color.z * 255); + ((int)(color.x * 255) << 16) | + ((int)(color.y * 255) << 8) | + (int)(color.z * 255); } @@ -391,6 +432,14 @@ static protected class OBJFace { } + static protected String getBasePath(PApplet parent, String filename) { + if (-1 < filename.indexOf(File.separator)) { + return filename.substring(0, filename.lastIndexOf(File.separator)); + } + return ""; + } + + // Stores a material defined in an MTL file. static protected class OBJMaterial { String name; @@ -416,3 +465,4 @@ static protected class OBJMaterial { } } } + diff --git a/libs/processing-core/src/main/java/processing/core/PShapeSVG.java b/libs/processing-core/src/main/java/processing/core/PShapeSVG.java new file mode 100644 index 000000000..33bd932b8 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/core/PShapeSVG.java @@ -0,0 +1,2031 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2006-12 Ben Fry and Casey Reas + Copyright (c) 2004-06 Michael Chang + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +//import static java.awt.Font.BOLD; +//import static java.awt.Font.ITALIC; +//import static java.awt.Font.PLAIN; +import processing.data.*; + +// TODO replace these with PMatrix2D +import android.graphics.Matrix; +//import java.awt.geom.AffineTransform; +//import java.awt.geom.Point2D; + +import java.util.Map; +import java.util.HashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * This class is not part of the Processing API and should not be used + * directly. Instead, use loadShape() and methods like it, which will make + * use of this class. Using this class directly will cause your code to break + * when combined with future versions of Processing. + *

    + * SVG stands for Scalable Vector Graphics, a portable graphics format. + * It is a vector format so it allows for "infinite" resolution and relatively + * small file sizes. Most modern media software can view SVG files, including + * Adobe products, Firefox, etc. Illustrator and Inkscape can edit SVG files. + * View the SVG specification here. + *

    + * We have no intention of turning this into a full-featured SVG library. + * The goal of this project is a basic shape importer that originally was small + * enough to be included with applets, meaning that its download size should be + * in the neighborhood of 25-30 Kb. Though we're far less limited nowadays on + * size constraints, we remain extremely limited in terms of time, and do not + * have volunteers who are available to maintain a larger SVG library. + *

    + * For more sophisticated import/export, consider the + * Batik + * library from the Apache Software Foundation. + *

    + * Batik is used in the SVG Export library in Processing 3, however using it + * for full SVG import is still a considerable amount of work. Wiring it to + * Java2D wouldn't be too bad, but using it with OpenGL, JavaFX, and features + * like begin/endRecord() and begin/endRaw() would be considerable effort. + *

    + * Future improvements to this library may focus on this properly supporting + * a specific subset of SVG, for instance the simpler SVG profiles known as + * SVG Tiny or Basic, + * although we still would not support the interactivity options. + * + *


    + * + * A minimal example program using SVG: + * (assuming a working moo.svg is in your data folder) + * + *

    + * PShape moo;
    + *
    + * void setup() {
    + *   size(400, 400);
    + *   moo = loadShape("moo.svg");
    + * }
    + * void draw() {
    + *   background(255);
    + *   shape(moo, mouseX, mouseY);
    + * }
    + * 
    + */ +public class PShapeSVG extends PShape { + XML element; + + /// Values between 0 and 1. + protected float opacity; + float strokeOpacity; + float fillOpacity; + + /** Width of containing SVG (used for percentages). */ + protected float svgWidth; + + /** Height of containing SVG (used for percentages). */ + protected float svgHeight; + + /** √((w² + h²)/2) of containing SVG (used for percentages). */ + protected float svgSizeXY; + + protected Gradient strokeGradient; + String strokeName; // id of another object, gradients only? + + protected Gradient fillGradient; + String fillName; // id of another object + + + /** + * Initializes a new SVG object from the given XML object. + */ + public PShapeSVG(XML svg) { + this(null, svg, true); + + if (!svg.getName().equals("svg")) { + if (svg.getName().toLowerCase().equals("html")) { + // Common case is that files aren't downloaded properly + throw new RuntimeException("This appears to be a web page, not an SVG file."); + } else { + throw new RuntimeException("The root node is not , it's <" + svg.getName() + ">"); + } + } + } + + + protected PShapeSVG(PShapeSVG parent, XML properties, boolean parseKids) { + setParent(parent); + + // Need to get width/height in early. + if (properties.getName().equals("svg")) { + String unitWidth = properties.getString("width"); + String unitHeight = properties.getString("height"); + + // Can't handle width/height as percentages easily. I'm just going + // to put in 100 as a dummy value, beacuse this means that it will + // come out as a reasonable value. + if (unitWidth != null) width = parseUnitSize(unitWidth, 100); + if (unitHeight != null) height = parseUnitSize(unitHeight, 100); + + String viewBoxStr = properties.getString("viewBox"); + if (viewBoxStr != null) { + float[] viewBox = PApplet.parseFloat(PApplet.splitTokens(viewBoxStr)); + if (unitWidth == null || unitHeight == null) { + // Not proper parsing of the viewBox, but will cover us for cases where + // the width and height of the object is not specified. + width = viewBox[2]; + height = viewBox[3]; + } else { + // http://www.w3.org/TR/SVG/coords.html#ViewBoxAttribute + // TODO: preserveAspectRatio. + if (matrix == null) matrix = new PMatrix2D(); + matrix.scale(width/viewBox[2], height/viewBox[3]); + matrix.translate(-viewBox[0], -viewBox[1]); + } + } + + // Negative size is illegal. + if (width < 0 || height < 0) + throw new RuntimeException(": width (" + width + + ") and height (" + height + ") must not be negative."); + + // It's technically valid to have width or height == 0. Not specified at + // all is what to test for. + if ((unitWidth == null || unitHeight == null) && viewBoxStr == null) { + //throw new RuntimeException("width/height not specified"); + PGraphics.showWarning("The width and/or height is not " + + "readable in the tag of this file."); + // For the spec, the default is 100% and 100%. For purposes + // here, insert a dummy value because this is prolly just a + // font or something for which the w/h doesn't matter. + width = 1; + height = 1; + } + + svgWidth = width; + svgHeight = height; + svgSizeXY = PApplet.sqrt((svgWidth*svgWidth + svgHeight*svgHeight)/2.0f); + } + + element = properties; + name = properties.getString("id"); + // @#$(* adobe illustrator mangles names of objects when re-saving + if (name != null) { + while (true) { + String[] m = PApplet.match(name, "_x([A-Za-z0-9]{2})_"); + if (m == null) break; + char repair = (char) PApplet.unhex(m[1]); + name = name.replace(m[0], "" + repair); + } + } + + String displayStr = properties.getString("display", "inline"); + visible = !displayStr.equals("none"); + + String transformStr = properties.getString("transform"); + if (transformStr != null) { + if (matrix == null) { + matrix = parseTransform(transformStr); + } else { + matrix.preApply(parseTransform(transformStr)); + } + } + + if (parseKids) { + parseColors(properties); + parseChildren(properties); + } + } + + + // Broken out so that subclasses can copy any additional variables + // (i.e. fillGradientPaint and strokeGradientPaint) + protected void setParent(PShapeSVG parent) { + // Need to set this so that findChild() works. + // Otherwise 'parent' is null until addChild() is called later. + this.parent = parent; + + if (parent == null) { + // set values to their defaults according to the SVG spec + stroke = false; + strokeColor = 0xff000000; + strokeWeight = 1; + strokeCap = PConstants.SQUARE; // equivalent to BUTT in svg spec + strokeJoin = PConstants.MITER; + strokeGradient = null; +// strokeGradientPaint = null; + strokeName = null; + + fill = true; + fillColor = 0xff000000; + fillGradient = null; +// fillGradientPaint = null; + fillName = null; + + //hasTransform = false; + //transformation = null; //new float[] { 1, 0, 0, 1, 0, 0 }; + + // svgWidth, svgHeight, and svgXYSize done below. + + strokeOpacity = 1; + fillOpacity = 1; + opacity = 1; + + } else { + stroke = parent.stroke; + strokeColor = parent.strokeColor; + strokeWeight = parent.strokeWeight; + strokeCap = parent.strokeCap; + strokeJoin = parent.strokeJoin; + strokeGradient = parent.strokeGradient; +// strokeGradientPaint = parent.strokeGradientPaint; + strokeName = parent.strokeName; + + fill = parent.fill; + fillColor = parent.fillColor; + fillGradient = parent.fillGradient; +// fillGradientPaint = parent.fillGradientPaint; + fillName = parent.fillName; + + svgWidth = parent.svgWidth; + svgHeight = parent.svgHeight; + svgSizeXY = parent.svgSizeXY; + + opacity = parent.opacity; + } + + // The rect and ellipse modes are set to CORNER since it is the expected + // mode for svg shapes. + rectMode = CORNER; + ellipseMode = CORNER; + } + + + /** Factory method for subclasses. */ + protected PShapeSVG createShape(PShapeSVG parent, XML properties, boolean parseKids) { + return new PShapeSVG(parent, properties, parseKids); + } + + + protected void parseChildren(XML graphics) { + XML[] elements = graphics.getChildren(); + children = new PShape[elements.length]; + childCount = 0; + + for (XML elem : elements) { + PShape kid = parseChild(elem); + if (kid != null) addChild(kid); + } + children = (PShape[]) PApplet.subset(children, 0, childCount); + } + + + /** + * Parse a child XML element. + * Override this method to add parsing for more SVG elements. + */ + protected PShape parseChild(XML elem) { +// System.err.println("parsing child in pshape " + elem.getName()); + String name = elem.getName(); + PShapeSVG shape = null; + + + if (name == null) { + // just some whitespace that can be ignored (hopefully) + + } else if (name.equals("g")) { + shape = createShape(this, elem, true); + + } else if (name.equals("defs")) { + // generally this will contain gradient info, so may + // as well just throw it into a group element for parsing + shape = createShape(this, elem, true); + + } else if (name.equals("line")) { + shape = createShape(this, elem, true); + shape.parseLine(); + + } else if (name.equals("circle")) { + shape = createShape(this, elem, true); + shape.parseEllipse(true); + + } else if (name.equals("ellipse")) { + shape = createShape(this, elem, true); + shape.parseEllipse(false); + + } else if (name.equals("rect")) { + shape = createShape(this, elem, true); + shape.parseRect(); + + } else if (name.equals("image")) { + shape = createShape(this, elem, true); + shape.parseImage(); + + } else if (name.equals("polygon")) { + shape = createShape(this, elem, true); + shape.parsePoly(true); + + } else if (name.equals("polyline")) { + shape = createShape(this, elem, true); + shape.parsePoly(false); + + } else if (name.equals("path")) { + shape = createShape(this, elem, true); + shape.parsePath(); + + } else if (name.equals("radialGradient")) { + return new RadialGradient(this, elem); + + } else if (name.equals("linearGradient")) { + return new LinearGradient(this, elem); + + } else if (name.equals("font")) { + return new Font(this, elem); + +// } else if (name.equals("font-face")) { +// return new FontFace(this, elem); + +// } else if (name.equals("glyph") || name.equals("missing-glyph")) { +// return new FontGlyph(this, elem); + + } else if (name.equals("text")) { // || name.equals("font")) { + return new Text(this, elem); + + } else if (name.equals("tspan")) { +// return new LineOfText(this, elem); + PGraphics.showWarning("tspan elements are not supported."); + + } else if (name.equals("filter")) { + PGraphics.showWarning("Filters are not supported."); + + } else if (name.equals("mask")) { + PGraphics.showWarning("Masks are not supported."); + + } else if (name.equals("pattern")) { + PGraphics.showWarning("Patterns are not supported."); + + } else if (name.equals("stop")) { + // stop tag is handled by gradient parser, so don't warn about it + + } else if (name.equals("sodipodi:namedview")) { + // these are always in Inkscape files, the warnings get tedious + + } else if (name.equals("metadata") + || name.equals("title") || name.equals("desc")) { + // fontforge just stuffs in as a comment. + // All harmless stuff, irrelevant to rendering. + return null; + + } else if (!name.startsWith("#")) { + PGraphics.showWarning("Ignoring <" + name + "> tag."); +// new Exception().printStackTrace(); + } + return shape; + } + + + protected void parseLine() { + kind = LINE; + family = PRIMITIVE; + params = new float[] { + getFloatWithUnit(element, "x1", svgWidth), + getFloatWithUnit(element, "y1", svgHeight), + getFloatWithUnit(element, "x2", svgWidth), + getFloatWithUnit(element, "y2", svgHeight) + }; + } + + + /** + * Handles parsing ellipse and circle tags. + * @param circle true if this is a circle and not an ellipse + */ + protected void parseEllipse(boolean circle) { + kind = ELLIPSE; + family = PRIMITIVE; + params = new float[4]; + + params[0] = getFloatWithUnit(element, "cx", svgWidth); + params[1] = getFloatWithUnit(element, "cy", svgHeight); + + float rx, ry; + if (circle) { + rx = ry = getFloatWithUnit(element, "r", svgSizeXY); + } else { + rx = getFloatWithUnit(element, "rx", svgWidth); + ry = getFloatWithUnit(element, "ry", svgHeight); + } + params[0] -= rx; + params[1] -= ry; + + params[2] = rx*2; + params[3] = ry*2; + } + + + protected void parseRect() { + kind = RECT; + family = PRIMITIVE; + params = new float[] { + getFloatWithUnit(element, "x", svgWidth), + getFloatWithUnit(element, "y", svgHeight), + getFloatWithUnit(element, "width", svgWidth), + getFloatWithUnit(element, "height", svgHeight) + }; + } + + + protected void parseImage() { + kind = RECT; + textureMode = NORMAL; + + family = PRIMITIVE; + params = new float[] { + getFloatWithUnit(element, "x", svgWidth), + getFloatWithUnit(element, "y", svgHeight), + getFloatWithUnit(element, "width", svgWidth), + getFloatWithUnit(element, "height", svgHeight) + }; + + this.imagePath = element.getString("xlink:href"); + } + + /** + * Parse a polyline or polygon from an SVG file. + * Syntax defined at http://www.w3.org/TR/SVG/shapes.html#PointsBNF + * @param close true if shape is closed (polygon), false if not (polyline) + */ + protected void parsePoly(boolean close) { + family = PATH; + this.close = close; + + String pointsAttr = element.getString("points"); + if (pointsAttr != null) { + Pattern pattern = Pattern.compile("([+-]?[\\d]+(\\.[\\d]+)?([eE][+-][\\d]+)?)(,?\\s*)([+-]?[\\d]+(\\.[\\d]+)?([eE][+-][\\d]+)?)"); + Matcher matcher = pattern.matcher(pointsAttr); + vertexCount = 0; + while (matcher.find()) { + vertexCount++; + } + matcher.reset(); + vertices = new float[vertexCount][2]; + for (int i = 0; i < vertexCount; i++) { + matcher.find(); + vertices[i][X] = Float.parseFloat(matcher.group(1)); + vertices[i][Y] = Float.parseFloat(matcher.group(5)); + } +// String[] pointsBuffer = PApplet.splitTokens(pointsAttr); +// vertexCount = pointsBuffer.length; +// vertices = new float[vertexCount][2]; +// for (int i = 0; i < vertexCount; i++) { +// String pb[] = PApplet.splitTokens(pointsBuffer[i], ", \t\r\n"); +// vertices[i][X] = Float.parseFloat(pb[0]); +// vertices[i][Y] = Float.parseFloat(pb[1]); +// } + } + } + + + protected void parsePath() { + family = PATH; + kind = 0; + + String pathData = element.getString("d"); + if (pathData == null || PApplet.trim(pathData).length() == 0) { + return; + } + char[] pathDataChars = pathData.toCharArray(); + + StringBuilder pathBuffer = new StringBuilder(); + boolean lastSeparate = false; + + for (int i = 0; i < pathDataChars.length; i++) { + char c = pathDataChars[i]; + boolean separate = false; + + if (c == 'M' || c == 'm' || + c == 'L' || c == 'l' || + c == 'H' || c == 'h' || + c == 'V' || c == 'v' || + c == 'C' || c == 'c' || // beziers + c == 'S' || c == 's' || + c == 'Q' || c == 'q' || // quadratic beziers + c == 'T' || c == 't' || + c == 'A' || c == 'a' || // elliptical arc + c == 'Z' || c == 'z' || // closepath + c == ',') { + separate = true; + if (i != 0) { + pathBuffer.append("|"); + } + } + if (c == 'Z' || c == 'z') { + separate = false; + } + if (c == '-' && !lastSeparate) { + // allow for 'e' notation in numbers, e.g. 2.10e-9 + // http://dev.processing.org/bugs/show_bug.cgi?id=1408 + if (i == 0 || pathDataChars[i-1] != 'e') { + pathBuffer.append("|"); + } + } + if (c != ',') { + pathBuffer.append(c); //"" + pathDataBuffer.charAt(i)); + } + if (separate && c != ',' && c != '-') { + pathBuffer.append("|"); + } + lastSeparate = separate; + } + + // use whitespace constant to get rid of extra spaces and CR or LF + String[] pathTokens = + PApplet.splitTokens(pathBuffer.toString(), "|" + WHITESPACE); + vertices = new float[pathTokens.length][2]; + vertexCodes = new int[pathTokens.length]; + + float cx = 0; + float cy = 0; + int i = 0; + + char implicitCommand = '\0'; +// char prevCommand = '\0'; + boolean prevCurve = false; + float ctrlX, ctrlY; + // store values for closepath so that relative coords work properly + float movetoX = 0; + float movetoY = 0; + + while (i < pathTokens.length) { + char c = pathTokens[i].charAt(0); + if (((c >= '0' && c <= '9') || (c == '-')) && implicitCommand != '\0') { + c = implicitCommand; + i--; + } else { + implicitCommand = c; + } + switch (c) { + + case 'M': // M - move to (absolute) + cx = PApplet.parseFloat(pathTokens[i + 1]); + cy = PApplet.parseFloat(pathTokens[i + 2]); + movetoX = cx; + movetoY = cy; + parsePathMoveto(cx, cy); + implicitCommand = 'L'; + i += 3; + break; + + case 'm': // m - move to (relative) + cx = cx + PApplet.parseFloat(pathTokens[i + 1]); + cy = cy + PApplet.parseFloat(pathTokens[i + 2]); + movetoX = cx; + movetoY = cy; + parsePathMoveto(cx, cy); + implicitCommand = 'l'; + i += 3; + break; + + case 'L': + cx = PApplet.parseFloat(pathTokens[i + 1]); + cy = PApplet.parseFloat(pathTokens[i + 2]); + parsePathLineto(cx, cy); + i += 3; + break; + + case 'l': + cx = cx + PApplet.parseFloat(pathTokens[i + 1]); + cy = cy + PApplet.parseFloat(pathTokens[i + 2]); + parsePathLineto(cx, cy); + i += 3; + break; + + // horizontal lineto absolute + case 'H': + cx = PApplet.parseFloat(pathTokens[i + 1]); + parsePathLineto(cx, cy); + i += 2; + break; + + // horizontal lineto relative + case 'h': + cx = cx + PApplet.parseFloat(pathTokens[i + 1]); + parsePathLineto(cx, cy); + i += 2; + break; + + case 'V': + cy = PApplet.parseFloat(pathTokens[i + 1]); + parsePathLineto(cx, cy); + i += 2; + break; + + case 'v': + cy = cy + PApplet.parseFloat(pathTokens[i + 1]); + parsePathLineto(cx, cy); + i += 2; + break; + + // C - curve to (absolute) + case 'C': { + float ctrlX1 = PApplet.parseFloat(pathTokens[i + 1]); + float ctrlY1 = PApplet.parseFloat(pathTokens[i + 2]); + float ctrlX2 = PApplet.parseFloat(pathTokens[i + 3]); + float ctrlY2 = PApplet.parseFloat(pathTokens[i + 4]); + float endX = PApplet.parseFloat(pathTokens[i + 5]); + float endY = PApplet.parseFloat(pathTokens[i + 6]); + parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); + cx = endX; + cy = endY; + i += 7; + prevCurve = true; + } + break; + + // c - curve to (relative) + case 'c': { + float ctrlX1 = cx + PApplet.parseFloat(pathTokens[i + 1]); + float ctrlY1 = cy + PApplet.parseFloat(pathTokens[i + 2]); + float ctrlX2 = cx + PApplet.parseFloat(pathTokens[i + 3]); + float ctrlY2 = cy + PApplet.parseFloat(pathTokens[i + 4]); + float endX = cx + PApplet.parseFloat(pathTokens[i + 5]); + float endY = cy + PApplet.parseFloat(pathTokens[i + 6]); + parsePathCurveto(ctrlX1, ctrlY1, ctrlX2, ctrlY2, endX, endY); + cx = endX; + cy = endY; + i += 7; + prevCurve = true; + } + break; + + // S - curve to shorthand (absolute) + // Draws a cubic Bézier curve from the current point to (x,y). The first + // control point is assumed to be the reflection of the second control + // point on the previous command relative to the current point. + // (x2,y2) is the second control point (i.e., the control point + // at the end of the curve). S (uppercase) indicates that absolute + // coordinates will follow; s (lowercase) indicates that relative + // coordinates will follow. Multiple sets of coordinates may be specified + // to draw a polybézier. At the end of the command, the new current point + // becomes the final (x,y) coordinate pair used in the polybézier. + case 'S': { + // (If there is no previous command or if the previous command was not + // an C, c, S or s, assume the first control point is coincident with + // the current point.) + if (!prevCurve) { + ctrlX = cx; + ctrlY = cy; + } else { + float ppx = vertices[vertexCount-2][X]; + float ppy = vertices[vertexCount-2][Y]; + float px = vertices[vertexCount-1][X]; + float py = vertices[vertexCount-1][Y]; + ctrlX = px + (px - ppx); + ctrlY = py + (py - ppy); + } + float ctrlX2 = PApplet.parseFloat(pathTokens[i + 1]); + float ctrlY2 = PApplet.parseFloat(pathTokens[i + 2]); + float endX = PApplet.parseFloat(pathTokens[i + 3]); + float endY = PApplet.parseFloat(pathTokens[i + 4]); + parsePathCurveto(ctrlX, ctrlY, ctrlX2, ctrlY2, endX, endY); + cx = endX; + cy = endY; + i += 5; + prevCurve = true; + } + break; + + // s - curve to shorthand (relative) + case 's': { + if (!prevCurve) { + ctrlX = cx; + ctrlY = cy; + } else { + float ppx = vertices[vertexCount-2][X]; + float ppy = vertices[vertexCount-2][Y]; + float px = vertices[vertexCount-1][X]; + float py = vertices[vertexCount-1][Y]; + ctrlX = px + (px - ppx); + ctrlY = py + (py - ppy); + } + float ctrlX2 = cx + PApplet.parseFloat(pathTokens[i + 1]); + float ctrlY2 = cy + PApplet.parseFloat(pathTokens[i + 2]); + float endX = cx + PApplet.parseFloat(pathTokens[i + 3]); + float endY = cy + PApplet.parseFloat(pathTokens[i + 4]); + parsePathCurveto(ctrlX, ctrlY, ctrlX2, ctrlY2, endX, endY); + cx = endX; + cy = endY; + i += 5; + prevCurve = true; + } + break; + + // Q - quadratic curve to (absolute) + // Draws a quadratic Bézier curve from the current point to (x,y) using + // (x1,y1) as the control point. Q (uppercase) indicates that absolute + // coordinates will follow; q (lowercase) indicates that relative + // coordinates will follow. Multiple sets of coordinates may be specified + // to draw a polybézier. At the end of the command, the new current point + // becomes the final (x,y) coordinate pair used in the polybézier. + case 'Q': { + ctrlX = PApplet.parseFloat(pathTokens[i + 1]); + ctrlY = PApplet.parseFloat(pathTokens[i + 2]); + float endX = PApplet.parseFloat(pathTokens[i + 3]); + float endY = PApplet.parseFloat(pathTokens[i + 4]); + //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + parsePathQuadto(ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + i += 5; + prevCurve = true; + } + break; + + // q - quadratic curve to (relative) + case 'q': { + ctrlX = cx + PApplet.parseFloat(pathTokens[i + 1]); + ctrlY = cy + PApplet.parseFloat(pathTokens[i + 2]); + float endX = cx + PApplet.parseFloat(pathTokens[i + 3]); + float endY = cy + PApplet.parseFloat(pathTokens[i + 4]); + //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + parsePathQuadto(ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + i += 5; + prevCurve = true; + } + break; + + // T - quadratic curveto shorthand (absolute) + // The control point is assumed to be the reflection of the control + // point on the previous command relative to the current point. + case 'T': { + // If there is no previous command or if the previous command was + // not a Q, q, T or t, assume the control point is coincident + // with the current point. + if (!prevCurve) { + ctrlX = cx; + ctrlY = cy; + } else { + float ppx = vertices[vertexCount-2][X]; + float ppy = vertices[vertexCount-2][Y]; + float px = vertices[vertexCount-1][X]; + float py = vertices[vertexCount-1][Y]; + ctrlX = px + (px - ppx); + ctrlY = py + (py - ppy); + } + float endX = PApplet.parseFloat(pathTokens[i + 1]); + float endY = PApplet.parseFloat(pathTokens[i + 2]); + //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + parsePathQuadto(ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + i += 3; + prevCurve = true; + } + break; + + // t - quadratic curveto shorthand (relative) + case 't': { + if (!prevCurve) { + ctrlX = cx; + ctrlY = cy; + } else { + float ppx = vertices[vertexCount-2][X]; + float ppy = vertices[vertexCount-2][Y]; + float px = vertices[vertexCount-1][X]; + float py = vertices[vertexCount-1][Y]; + ctrlX = px + (px - ppx); + ctrlY = py + (py - ppy); + } + float endX = cx + PApplet.parseFloat(pathTokens[i + 1]); + float endY = cy + PApplet.parseFloat(pathTokens[i + 2]); + //parsePathQuadto(cx, cy, ctrlX, ctrlY, endX, endY); + parsePathQuadto(ctrlX, ctrlY, endX, endY); + cx = endX; + cy = endY; + i += 3; + prevCurve = true; + } + break; + + // A - elliptical arc to (absolute) + case 'A': { + float rx = PApplet.parseFloat(pathTokens[i + 1]); + float ry = PApplet.parseFloat(pathTokens[i + 2]); + float angle = PApplet.parseFloat(pathTokens[i + 3]); + boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0; + boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0; + float endX = PApplet.parseFloat(pathTokens[i + 6]); + float endY = PApplet.parseFloat(pathTokens[i + 7]); + parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY); + cx = endX; + cy = endY; + i += 8; + prevCurve = true; + } + break; + + // a - elliptical arc to (relative) + case 'a': { + float rx = PApplet.parseFloat(pathTokens[i + 1]); + float ry = PApplet.parseFloat(pathTokens[i + 2]); + float angle = PApplet.parseFloat(pathTokens[i + 3]); + boolean fa = PApplet.parseFloat(pathTokens[i + 4]) != 0; + boolean fs = PApplet.parseFloat(pathTokens[i + 5]) != 0; + float endX = cx + PApplet.parseFloat(pathTokens[i + 6]); + float endY = cy + PApplet.parseFloat(pathTokens[i + 7]); + parsePathArcto(cx, cy, rx, ry, angle, fa, fs, endX, endY); + cx = endX; + cy = endY; + i += 8; + prevCurve = true; + } + break; + + case 'Z': + case 'z': + // since closing the path, the 'current' point needs + // to return back to the last moveto location. + // http://code.google.com/p/processing/issues/detail?id=1058 + cx = movetoX; + cy = movetoY; + close = true; + i++; + break; + + default: + String parsed = + PApplet.join(PApplet.subset(pathTokens, 0, i), ","); + String unparsed = + PApplet.join(PApplet.subset(pathTokens, i), ","); + System.err.println("parsed: " + parsed); + System.err.println("unparsed: " + unparsed); + throw new RuntimeException("shape command not handled: " + pathTokens[i]); + } +// prevCommand = c; + } + } + + +// private void parsePathCheck(int num) { +// if (vertexCount + num-1 >= vertices.length) { +// //vertices = (float[][]) PApplet.expand(vertices); +// float[][] temp = new float[vertexCount << 1][2]; +// System.arraycopy(vertices, 0, temp, 0, vertexCount); +// vertices = temp; +// } +// } + + private void parsePathVertex(float x, float y) { + if (vertexCount == vertices.length) { + //vertices = (float[][]) PApplet.expand(vertices); + float[][] temp = new float[vertexCount << 1][2]; + System.arraycopy(vertices, 0, temp, 0, vertexCount); + vertices = temp; + } + vertices[vertexCount][X] = x; + vertices[vertexCount][Y] = y; + vertexCount++; + } + + + private void parsePathCode(int what) { + if (vertexCodeCount == vertexCodes.length) { + vertexCodes = PApplet.expand(vertexCodes); + } + vertexCodes[vertexCodeCount++] = what; + } + + + private void parsePathMoveto(float px, float py) { + if (vertexCount > 0) { + parsePathCode(BREAK); + } + parsePathCode(VERTEX); + parsePathVertex(px, py); + } + + + private void parsePathLineto(float px, float py) { + parsePathCode(VERTEX); + parsePathVertex(px, py); + } + + + private void parsePathCurveto(float x1, float y1, + float x2, float y2, + float x3, float y3) { + parsePathCode(BEZIER_VERTEX); + parsePathVertex(x1, y1); + parsePathVertex(x2, y2); + parsePathVertex(x3, y3); + } + +// private void parsePathQuadto(float x1, float y1, +// float cx, float cy, +// float x2, float y2) { +// //System.out.println("quadto: " + x1 + "," + y1 + " " + cx + "," + cy + " " + x2 + "," + y2); +//// parsePathCode(BEZIER_VERTEX); +// parsePathCode(QUAD_BEZIER_VERTEX); +// // x1/y1 already covered by last moveto, lineto, or curveto +// +// parsePathVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f)); +// parsePathVertex(x2 + ((cx-x2)*2/3.0f), y2 + ((cy-y2)*2/3.0f)); +// parsePathVertex(x2, y2); +// } + + private void parsePathQuadto(float cx, float cy, + float x2, float y2) { + //System.out.println("quadto: " + x1 + "," + y1 + " " + cx + "," + cy + " " + x2 + "," + y2); +// parsePathCode(BEZIER_VERTEX); + parsePathCode(QUADRATIC_VERTEX); + // x1/y1 already covered by last moveto, lineto, or curveto + parsePathVertex(cx, cy); + parsePathVertex(x2, y2); + } + + + // Approximates elliptical arc by several bezier segments. + // Meets SVG standard requirements from: + // http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands + // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes + // Based on arc to bezier curve equations from: + // http://www.spaceroots.org/documents/ellipse/node22.html + private void parsePathArcto(float x1, float y1, + float rx, float ry, + float angle, + boolean fa, boolean fs, + float x2, float y2) { + if (x1 == x2 && y1 == y2) return; + if (rx == 0 || ry == 0) { parsePathLineto(x2, y2); return; } + + rx = PApplet.abs(rx); ry = PApplet.abs(ry); + + float phi = PApplet.radians(((angle % 360) + 360) % 360); + float cosPhi = PApplet.cos(phi), sinPhi = PApplet.sin(phi); + + float x1r = ( cosPhi * (x1 - x2) + sinPhi * (y1 - y2)) / 2; + float y1r = (-sinPhi * (x1 - x2) + cosPhi * (y1 - y2)) / 2; + + float cxr, cyr; + { + float A = (x1r*x1r) / (rx*rx) + (y1r*y1r) / (ry*ry); + if (A > 1) { + // No solution, scale ellipse up according to SVG standard + float sqrtA = PApplet.sqrt(A); + rx *= sqrtA; cxr = 0; + ry *= sqrtA; cyr = 0; + } else { + float k = ((fa == fs) ? -1f : 1f) * + PApplet.sqrt((rx*rx * ry*ry) / ((rx*rx * y1r*y1r) + (ry*ry * x1r*x1r)) - 1f); + cxr = k * rx * y1r / ry; + cyr = -k * ry * x1r / rx; + } + } + + float cx = cosPhi * cxr - sinPhi * cyr + (x1 + x2) / 2; + float cy = sinPhi * cxr + cosPhi * cyr + (y1 + y2) / 2; + + float phi1, phiDelta; + { + float sx = ( x1r - cxr) / rx, sy = ( y1r - cyr) / ry; + float tx = (-x1r - cxr) / rx, ty = (-y1r - cyr) / ry; + phi1 = PApplet.atan2(sy, sx); + phiDelta = (((PApplet.atan2(ty, tx) - phi1) % TWO_PI) + TWO_PI) % TWO_PI; + if (!fs) phiDelta -= TWO_PI; + } + + // One segment can not cover more that PI, less than PI/2 is + // recommended to avoid visible inaccuracies caused by rounding errors + int segmentCount = PApplet.ceil(PApplet.abs(phiDelta) / TWO_PI * 4); + + float inc = phiDelta / segmentCount; + float a = PApplet.sin(inc) * + (PApplet.sqrt(4 + 3 * PApplet.sq(PApplet.tan(inc / 2))) - 1) / 3; + + float sinPhi1 = PApplet.sin(phi1), cosPhi1 = PApplet.cos(phi1); + + float p1x = x1; + float p1y = y1; + float relq1x = a * (-rx * cosPhi * sinPhi1 - ry * sinPhi * cosPhi1); + float relq1y = a * (-rx * sinPhi * sinPhi1 + ry * cosPhi * cosPhi1); + + for (int i = 0; i < segmentCount; i++) { + float eta = phi1 + (i + 1) * inc; + float sinEta = PApplet.sin(eta), cosEta = PApplet.cos(eta); + + float p2x = cx + rx * cosPhi * cosEta - ry * sinPhi * sinEta; + float p2y = cy + rx * sinPhi * cosEta + ry * cosPhi * sinEta; + float relq2x = a * (-rx * cosPhi * sinEta - ry * sinPhi * cosEta); + float relq2y = a * (-rx * sinPhi * sinEta + ry * cosPhi * cosEta); + + if (i == segmentCount - 1) { p2x = x2; p2y = y2; } + + parsePathCode(BEZIER_VERTEX); + parsePathVertex(p1x + relq1x, p1y + relq1y); + parsePathVertex(p2x - relq2x, p2y - relq2y); + parsePathVertex(p2x, p2y); + + p1x = p2x; relq1x = relq2x; + p1y = p2y; relq1y = relq2y; + } + } + + + /** + * Parse the specified SVG matrix into a PMatrix2D. Note that PMatrix2D + * is rotated relative to the SVG definition, so parameters are rearranged + * here. More about the transformation matrices in + * this section + * of the SVG documentation. + * @param matrixStr text of the matrix param. + * @return a good old-fashioned PMatrix2D + */ + static protected PMatrix2D parseTransform(String matrixStr) { + matrixStr = matrixStr.trim(); + PMatrix2D outgoing = null; + int start = 0; + int stop = -1; + while ((stop = matrixStr.indexOf(')', start)) != -1) { + PMatrix2D m = parseSingleTransform(matrixStr.substring(start, stop+1)); + if (outgoing == null) { + outgoing = m; + } else { + outgoing.apply(m); + } + start = stop + 1; + } + return outgoing; + } + + + static protected PMatrix2D parseSingleTransform(String matrixStr) { + //String[] pieces = PApplet.match(matrixStr, "^\\s*(\\w+)\\((.*)\\)\\s*$"); + String[] pieces = PApplet.match(matrixStr, "[,\\s]*(\\w+)\\((.*)\\)"); + if (pieces == null) { + System.err.println("Could not parse transform " + matrixStr); + return null; + } + float[] m = PApplet.parseFloat(PApplet.splitTokens(pieces[2], ", ")); + if (pieces[1].equals("matrix")) { + return new PMatrix2D(m[0], m[2], m[4], m[1], m[3], m[5]); + + } else if (pieces[1].equals("translate")) { + float tx = m[0]; + float ty = (m.length == 2) ? m[1] : m[0]; + return new PMatrix2D(1, 0, tx, 0, 1, ty); + + } else if (pieces[1].equals("scale")) { + float sx = m[0]; + float sy = (m.length == 2) ? m[1] : m[0]; + return new PMatrix2D(sx, 0, 0, 0, sy, 0); + + } else if (pieces[1].equals("rotate")) { + float angle = m[0]; + + if (m.length == 1) { + float c = PApplet.cos(angle); + float s = PApplet.sin(angle); + // SVG version is cos(a) sin(a) -sin(a) cos(a) 0 0 + return new PMatrix2D(c, -s, 0, s, c, 0); + + } else if (m.length == 3) { + PMatrix2D mat = new PMatrix2D(0, 1, m[1], 1, 0, m[2]); + mat.rotate(m[0]); + mat.translate(-m[1], -m[2]); + return mat; + } + + } else if (pieces[1].equals("skewX")) { + return new PMatrix2D(1, 0, 1, PApplet.tan(m[0]), 0, 0); + + } else if (pieces[1].equals("skewY")) { + return new PMatrix2D(1, 0, 1, 0, PApplet.tan(m[0]), 0); + } + return null; + } + + + protected void parseColors(XML properties) { + if (properties.hasAttribute("opacity")) { + String opacityText = properties.getString("opacity"); + setOpacity(opacityText); + } + + if (properties.hasAttribute("stroke")) { + String strokeText = properties.getString("stroke"); + setColor(strokeText, false); + } + + if (properties.hasAttribute("stroke-opacity")) { + String strokeOpacityText = properties.getString("stroke-opacity"); + setStrokeOpacity(strokeOpacityText); + } + + if (properties.hasAttribute("stroke-width")) { + // if NaN (i.e. if it's 'inherit') then default back to the inherit setting + String lineweight = properties.getString("stroke-width"); + setStrokeWeight(lineweight); + } + + if (properties.hasAttribute("stroke-linejoin")) { + String linejoin = properties.getString("stroke-linejoin"); + setStrokeJoin(linejoin); + } + + if (properties.hasAttribute("stroke-linecap")) { + String linecap = properties.getString("stroke-linecap"); + setStrokeCap(linecap); + } + + // fill defaults to black (though stroke defaults to "none") + // http://www.w3.org/TR/SVG/painting.html#FillProperties + if (properties.hasAttribute("fill")) { + String fillText = properties.getString("fill"); + setColor(fillText, true); + } + + if (properties.hasAttribute("fill-opacity")) { + String fillOpacityText = properties.getString("fill-opacity"); + setFillOpacity(fillOpacityText); + } + + if (properties.hasAttribute("style")) { + String styleText = properties.getString("style"); + String[] styleTokens = PApplet.splitTokens(styleText, ";"); + + //PApplet.println(styleTokens); + for (int i = 0; i < styleTokens.length; i++) { + String[] tokens = PApplet.splitTokens(styleTokens[i], ":"); + //PApplet.println(tokens); + + tokens[0] = PApplet.trim(tokens[0]); + + if (tokens[0].equals("fill")) { + setColor(tokens[1], true); + + } else if(tokens[0].equals("fill-opacity")) { + setFillOpacity(tokens[1]); + + } else if(tokens[0].equals("stroke")) { + setColor(tokens[1], false); + + } else if(tokens[0].equals("stroke-width")) { + setStrokeWeight(tokens[1]); + + } else if(tokens[0].equals("stroke-linecap")) { + setStrokeCap(tokens[1]); + + } else if(tokens[0].equals("stroke-linejoin")) { + setStrokeJoin(tokens[1]); + + } else if(tokens[0].equals("stroke-opacity")) { + setStrokeOpacity(tokens[1]); + + } else if(tokens[0].equals("opacity")) { + setOpacity(tokens[1]); + + } else { + // Other attributes are not yet implemented + } + } + } + } + + + void setOpacity(String opacityText) { + opacity = PApplet.parseFloat(opacityText); + strokeColor = ((int) (opacity * 255)) << 24 | strokeColor & 0xFFFFFF; + fillColor = ((int) (opacity * 255)) << 24 | fillColor & 0xFFFFFF; + } + + + void setStrokeWeight(String lineweight) { + strokeWeight = parseUnitSize(lineweight, svgSizeXY); + } + + + void setStrokeOpacity(String opacityText) { + strokeOpacity = PApplet.parseFloat(opacityText); + strokeColor = ((int) (strokeOpacity * 255)) << 24 | strokeColor & 0xFFFFFF; + } + + + void setStrokeJoin(String linejoin) { + if (linejoin.equals("inherit")) { + // do nothing, will inherit automatically + + } else if (linejoin.equals("miter")) { + strokeJoin = PConstants.MITER; + + } else if (linejoin.equals("round")) { + strokeJoin = PConstants.ROUND; + + } else if (linejoin.equals("bevel")) { + strokeJoin = PConstants.BEVEL; + } + } + + + void setStrokeCap(String linecap) { + if (linecap.equals("inherit")) { + // do nothing, will inherit automatically + + } else if (linecap.equals("butt")) { + strokeCap = PConstants.SQUARE; + + } else if (linecap.equals("round")) { + strokeCap = PConstants.ROUND; + + } else if (linecap.equals("square")) { + strokeCap = PConstants.PROJECT; + } + } + + + void setFillOpacity(String opacityText) { + fillOpacity = PApplet.parseFloat(opacityText); + fillColor = ((int) (fillOpacity * 255)) << 24 | fillColor & 0xFFFFFF; + } + + + void setColor(String colorText, boolean isFill) { + colorText = colorText.trim(); + int opacityMask = fillColor & 0xFF000000; + boolean visible = true; + int color = 0; + String name = ""; +// String lColorText = colorText.toLowerCase(); + Gradient gradient = null; +// Object paint = null; + if (colorText.equals("none")) { + visible = false; + } else if (colorText.startsWith("url(#")) { + name = colorText.substring(5, colorText.length() - 1); + Object object = findChild(name); + if (object instanceof Gradient) { + gradient = (Gradient) object; + // in 3.0a11, do this on first draw inside PShapeJava2D +// paint = calcGradientPaint(gradient); //, opacity); + } else { +// visible = false; + System.err.println("url " + name + " refers to unexpected data: " + object); + } + } else { + // Prints errors itself. + color = opacityMask | parseSimpleColor(colorText); + } + if (isFill) { + fill = visible; + fillColor = color; + fillName = name; + fillGradient = gradient; +// fillGradientPaint = paint; + } else { + stroke = visible; + strokeColor = color; + strokeName = name; + strokeGradient = gradient; +// strokeGradientPaint = paint; + } + } + + + /** + * Parses the "color" datatype only, and prints an error if it is not of this form. + * http://www.w3.org/TR/SVG/types.html#DataTypeColor + * @return 0xRRGGBB (no alpha). Zero on error. + */ + static protected int parseSimpleColor(String colorText) { + colorText = colorText.toLowerCase().trim(); + //if (colorNames.containsKey(colorText)) { + if (colorNames.hasKey(colorText)) { + return colorNames.get(colorText); + } else if (colorText.startsWith("#")) { + if (colorText.length() == 4) { + // Short form: #ABC, transform to long form #AABBCC + colorText = colorText.replaceAll("^#(.)(.)(.)$", "#$1$1$2$2$3$3"); + } + return (Integer.parseInt(colorText.substring(1), 16)) & 0xFFFFFF; + //System.out.println("hex for fill is " + PApplet.hex(fillColor)); + } else if (colorText.startsWith("rgb")) { + return parseRGB(colorText); + } else { + System.err.println("Cannot parse \"" + colorText + "\"."); + return 0; + } + } + + + /** + * Deliberately conforms to the HTML 4.01 color spec + en-gb grey, rather + * than the (unlikely to be useful) entire 147-color system used in SVG. + */ + static protected IntDict colorNames = new IntDict(new Object[][] { + { "aqua", 0x00ffff }, + { "black", 0x000000 }, + { "blue", 0x0000ff }, + { "fuchsia", 0xff00ff }, + { "gray", 0x808080 }, + { "grey", 0x808080 }, + { "green", 0x008000 }, + { "lime", 0x00ff00 }, + { "maroon", 0x800000 }, + { "navy", 0x000080 }, + { "olive", 0x808000 }, + { "purple", 0x800080 }, + { "red", 0xff0000 }, + { "silver", 0xc0c0c0 }, + { "teal", 0x008080 }, + { "white", 0xffffff }, + { "yellow", 0xffff00 } + }); + + /* + static protected Map colorNames; + static { + colorNames = new HashMap(); + colorNames.put("aqua", 0x00ffff); + colorNames.put("black", 0x000000); + colorNames.put("blue", 0x0000ff); + colorNames.put("fuchsia", 0xff00ff); + colorNames.put("gray", 0x808080); + colorNames.put("grey", 0x808080); + colorNames.put("green", 0x008000); + colorNames.put("lime", 0x00ff00); + colorNames.put("maroon", 0x800000); + colorNames.put("navy", 0x000080); + colorNames.put("olive", 0x808000); + colorNames.put("purple", 0x800080); + colorNames.put("red", 0xff0000); + colorNames.put("silver", 0xc0c0c0); + colorNames.put("teal", 0x008080); + colorNames.put("white", 0xffffff); + colorNames.put("yellow", 0xffff00); + } + */ + + static protected int parseRGB(String what) { + int leftParen = what.indexOf('(') + 1; + int rightParen = what.indexOf(')'); + String sub = what.substring(leftParen, rightParen); + String[] values = PApplet.splitTokens(sub, ", "); + int rgbValue = 0; + if (values.length == 3) { + // Color spec allows for rgb values to be percentages. + for (int i = 0; i < 3; i++) { + rgbValue <<= 8; + if (values[i].endsWith("%")) { + rgbValue |= (int)(PApplet.constrain(255*parseFloatOrPercent(values[i]), 0, 255)); + } else { + rgbValue |= PApplet.constrain(PApplet.parseInt(values[i]), 0, 255); + } + } + } else System.err.println("Could not read color \"" + what + "\"."); + + return rgbValue; + } + + + //static protected Map parseStyleAttributes(String style) { + static protected StringDict parseStyleAttributes(String style) { + //Map table = new HashMap(); + StringDict table = new StringDict(); +// if (style == null) return table; + if (style != null) { + String[] pieces = style.split(";"); + for (int i = 0; i < pieces.length; i++) { + String[] parts = pieces[i].split(":"); + //table.put(parts[0], parts[1]); + table.set(parts[0], parts[1]); + } + } + return table; + } + + + /** + * Used in place of element.getFloatAttribute(a) because we can + * have a unit suffix (length or coordinate). + * @param element what to parse + * @param attribute name of the attribute to get + * @param relativeTo (float) Used for %. When relative to viewbox, should + * be svgWidth for horizontal dimentions, svgHeight for vertical, and + * svgXYSize for anything else. + * @return unit-parsed version of the data + */ + static protected float getFloatWithUnit(XML element, String attribute, float relativeTo) { + String val = element.getString(attribute); + return (val == null) ? 0 : parseUnitSize(val, relativeTo); + } + + + /** + * Parse a size that may have a suffix for its units. + * This assumes 90dpi, which implies, as given in the + * units spec: + *
      + *
    • "1pt" equals "1.25px" (and therefore 1.25 user units) + *
    • "1pc" equals "15px" (and therefore 15 user units) + *
    • "1mm" would be "3.543307px" (3.543307 user units) + *
    • "1cm" equals "35.43307px" (and therefore 35.43307 user units) + *
    • "1in" equals "90px" (and therefore 90 user units) + *
    + * @param relativeTo (float) Used for %. When relative to viewbox, should + * be svgWidth for horizontal dimentions, svgHeight for vertical, and + * svgXYSize for anything else. + */ + static protected float parseUnitSize(String text, float relativeTo) { + int len = text.length() - 2; + + if (text.endsWith("pt")) { + return PApplet.parseFloat(text.substring(0, len)) * 1.25f; + } else if (text.endsWith("pc")) { + return PApplet.parseFloat(text.substring(0, len)) * 15; + } else if (text.endsWith("mm")) { + return PApplet.parseFloat(text.substring(0, len)) * 3.543307f; + } else if (text.endsWith("cm")) { + return PApplet.parseFloat(text.substring(0, len)) * 35.43307f; + } else if (text.endsWith("in")) { + return PApplet.parseFloat(text.substring(0, len)) * 90; + } else if (text.endsWith("px")) { + return PApplet.parseFloat(text.substring(0, len)); + } else if (text.endsWith("%")) { + return relativeTo * parseFloatOrPercent(text); + } else { + return PApplet.parseFloat(text); + } + } + + + static protected float parseFloatOrPercent(String text) { + text = text.trim(); + if (text.endsWith("%")) { + return Float.parseFloat(text.substring(0, text.length() - 1)) / 100.0f; + } else { + return Float.parseFloat(text); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public class Gradient extends PShapeSVG { + Matrix transform; + + public float[] offset; + public int[] color; + public int count; + + public Gradient(PShapeSVG parent, XML properties) { + super(parent, properties, true); + + XML elements[] = properties.getChildren(); + offset = new float[elements.length]; + color = new int[elements.length]; + + // + for (int i = 0; i < elements.length; i++) { + XML elem = elements[i]; + String name = elem.getName(); + if (name.equals("stop")) { + String offsetAttr = elem.getString("offset"); + offset[count] = parseFloatOrPercent(offsetAttr); + + String style = elem.getString("style"); + //Map styles = parseStyleAttributes(style); + StringDict styles = parseStyleAttributes(style); + + String colorStr = styles.get("stop-color"); + if (colorStr == null) { + colorStr = elem.getString("stop-color"); + if (colorStr == null) colorStr = "#000000"; + } + String opacityStr = styles.get("stop-opacity"); + if (opacityStr == null) { + opacityStr = elem.getString("stop-opacity"); + if (opacityStr == null) opacityStr = "1"; + } + int tupacity = PApplet.constrain( + (int)(PApplet.parseFloat(opacityStr) * 255), 0, 255); + color[count] = (tupacity << 24) | parseSimpleColor(colorStr); + count++; + } + } + offset = PApplet.subset(offset, 0, count); + color = PApplet.subset(color, 0, count); + } + } + + + public class LinearGradient extends Gradient { + public float x1, y1, x2, y2; + + public LinearGradient(PShapeSVG parent, XML properties) { + super(parent, properties); + + this.x1 = getFloatWithUnit(properties, "x1", svgWidth); + this.y1 = getFloatWithUnit(properties, "y1", svgHeight); + this.x2 = getFloatWithUnit(properties, "x2", svgWidth); + this.y2 = getFloatWithUnit(properties, "y2", svgHeight); + + String transformStr = + properties.getString("gradientTransform"); + + if (transformStr != null) { + float t[] = parseTransform(transformStr).get(null); + //this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]); + transform = new Matrix(); + transform.setValues(new float[] { // TODO don't create temp floats + t[0], t[1], t[2], + t[3], t[4], t[5], + 0, 0, 1 + }); + +// Point2D t1 = transform.transform(new Point2D.Float(x1, y1), null); +// Point2D t2 = transform.transform(new Point2D.Float(x2, y2), null); + float[] t1 = new float[] { x1, y1 }; + float[] t2 = new float[] { x2, y2 }; + transform.mapPoints(t1); + transform.mapPoints(t2); + +// this.x1 = (float) t1.getX(); +// this.y1 = (float) t1.getY(); +// this.x2 = (float) t2.getX(); +// this.y2 = (float) t2.getY(); + x1 = t1[0]; + y1 = t1[1]; + x2 = t2[0]; + y2 = t2[1]; + } + } + } + + + public class RadialGradient extends Gradient { + public float cx, cy, r; + + public RadialGradient(PShapeSVG parent, XML properties) { + super(parent, properties); + + this.cx = getFloatWithUnit(properties, "cx", svgWidth); + this.cy = getFloatWithUnit(properties, "cy", svgHeight); + this.r = getFloatWithUnit(properties, "r", svgSizeXY); + + String transformStr = + properties.getString("gradientTransform"); + + if (transformStr != null) { + float t[] = parseTransform(transformStr).get(null); +// this.transform = new AffineTransform(t[0], t[3], t[1], t[4], t[2], t[5]); + transform = new Matrix(); + transform.setValues(new float[] { // TODO don't create temp floats + t[0], t[1], t[2], + t[3], t[4], t[5], + 0, 0, 1 + }); + +// Point2D t1 = transform.transform(new Point2D.Float(cx, cy), null); +// Point2D t2 = transform.transform(new Point2D.Float(cx + r, cy), null); + float[] t1 = new float[] { cx, cy }; + float[] t2 = new float[] { cx + r, cy }; + transform.mapPoints(t1); + transform.mapPoints(t2); + +// this.cx = (float) t1.getX(); +// this.cy = (float) t1.getY(); +// this.r = (float) (t2.getX() - t1.getX()); + cx = t1[0]; + cy = t1[1]; + r = t2[0] - t1[0]; + } + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + +// static private float TEXT_QUALITY = 1; + public static final int PLAIN = 0; + public static final int BOLD = 1; + public static final int ITALIC = 2; + + static private PFont parseFont(XML properties) { + String fontFamily = null; + float size = 10; + int weight = PLAIN; // 0 + int italic = 0; + + if (properties.hasAttribute("style")) { + String styleText = properties.getString("style"); + String[] styleTokens = PApplet.splitTokens(styleText, ";"); + + //PApplet.println(styleTokens); + for (int i = 0; i < styleTokens.length; i++) { + String[] tokens = PApplet.splitTokens(styleTokens[i], ":"); + //PApplet.println(tokens); + + tokens[0] = PApplet.trim(tokens[0]); + + if (tokens[0].equals("font-style")) { + // PApplet.println("font-style: " + tokens[1]); + if (tokens[1].contains("italic")) { + italic = ITALIC; + } + } else if (tokens[0].equals("font-variant")) { + // PApplet.println("font-variant: " + tokens[1]); + // setFillOpacity(tokens[1]); + + } else if (tokens[0].equals("font-weight")) { + // PApplet.println("font-weight: " + tokens[1]); + + if (tokens[1].contains("bold")) { + weight = BOLD; + // PApplet.println("Bold weight ! "); + } + + + } else if (tokens[0].equals("font-stretch")) { + // not supported. + + } else if (tokens[0].equals("font-size")) { + // PApplet.println("font-size: " + tokens[1]); + size = Float.parseFloat(tokens[1].split("px")[0]); + // PApplet.println("font-size-parsed: " + size); + } else if (tokens[0].equals("line-height")) { + // not supported + + } else if (tokens[0].equals("font-family")) { + // PApplet.println("Font-family: " + tokens[1]); + fontFamily = tokens[1]; + + } else if (tokens[0].equals("text-align")) { + // not supported + + } else if (tokens[0].equals("letter-spacing")) { + // not supported + + } else if (tokens[0].equals("word-spacing")) { + // not supported + + } else if (tokens[0].equals("writing-mode")) { + // not supported + + } else if (tokens[0].equals("text-anchor")) { + // not supported + + } else { + // Other attributes are not yet implemented + } + } + } + if (fontFamily == null) { + return null; + } +// size = size * TEXT_QUALITY; + + return createFont(fontFamily, weight | italic, size, true); + } + + + static protected PFont createFont(String name, int weight, + float size, boolean smooth) { + //System.out.println("Try to create a font of " + name + " family, " + weight); +// java.awt.Font baseFont = new java.awt.Font(name, weight, (int) size); + + //System.out.println("Resulting family : " + baseFont.getFamily() + " " + baseFont.getStyle()); +// return new PFont(baseFont.deriveFont(size), smooth, null); + return null; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public class Text extends PShapeSVG { + protected PFont font; + + public Text(PShapeSVG parent, XML properties) { + super(parent, properties, true); + + // get location + float x = Float.parseFloat(properties.getString("x")); + float y = Float.parseFloat(properties.getString("y")); + + if (matrix == null) { + matrix = new PMatrix2D(); + } + matrix.translate(x, y); + + family = GROUP; + + font = parseFont(properties); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public class LineOfText extends PShapeSVG { + String textToDisplay; + PFont font; + + public LineOfText(PShapeSVG parent, XML properties) { + // TODO: child should ideally be parsed too for inline content. + super(parent, properties, false); + + //get location + float x = Float.parseFloat(properties.getString("x")); + float y = Float.parseFloat(properties.getString("y")); + + float parentX = Float.parseFloat(parent.element.getString("x")); + float parentY = Float.parseFloat(parent.element.getString("y")); + + if (matrix == null) matrix = new PMatrix2D(); + matrix.translate(x - parentX, (y - parentY) / 2f); + + // get the first properties + parseColors(properties); + font = parseFont(properties); + + // cleaned up syntax but removing b/c unused [fry 190118] + //boolean isLine = properties.getString("role").equals("line"); + + if (this.childCount > 0) { + // no inline content yet. + } + + String text = properties.getContent(); + textToDisplay = text; + } + + @Override + public void drawImpl(PGraphics g) { + if (font == null) { + font = ((Text) parent).font; + if (font == null) { + return; + } + } + + pre(g); +// g.textFont(font, font.size / TEXT_QUALITY); + g.textFont(font, font.size); + g.text(textToDisplay, 0, 0); + post(g); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public class Font extends PShapeSVG { + public FontFace face; + + public Map namedGlyphs; + public Map unicodeGlyphs; + + public int glyphCount; + public FontGlyph[] glyphs; + public FontGlyph missingGlyph; + + int horizAdvX; + + + public Font(PShapeSVG parent, XML properties) { + super(parent, properties, false); +// handle(parent, properties); + + XML[] elements = properties.getChildren(); + + horizAdvX = properties.getInt("horiz-adv-x", 0); + + namedGlyphs = new HashMap<>(); + unicodeGlyphs = new HashMap<>(); + glyphCount = 0; + glyphs = new FontGlyph[elements.length]; + + for (int i = 0; i < elements.length; i++) { + String name = elements[i].getName(); + XML elem = elements[i]; + if (name == null) { + // skip it + } else if (name.equals("glyph")) { + FontGlyph fg = new FontGlyph(this, elem, this); + if (fg.isLegit()) { + if (fg.name != null) { + namedGlyphs.put(fg.name, fg); + } + if (fg.unicode != 0) { + unicodeGlyphs.put(Character.valueOf(fg.unicode), fg); + } + } + glyphs[glyphCount++] = fg; + + } else if (name.equals("missing-glyph")) { +// System.out.println("got missing glyph inside "); + missingGlyph = new FontGlyph(this, elem, this); + } else if (name.equals("font-face")) { + face = new FontFace(this, elem); + } else { + System.err.println("Ignoring " + name + " inside "); + } + } + } + + + protected void drawShape() { + // does nothing for fonts + } + + + public void drawString(PGraphics g, String str, float x, float y, float size) { + // 1) scale by the 1.0/unitsPerEm + // 2) scale up by a font size + g.pushMatrix(); + float s = size / face.unitsPerEm; + //System.out.println("scale is " + s); + // swap y coord at the same time, since fonts have y=0 at baseline + g.translate(x, y); + g.scale(s, -s); + char[] c = str.toCharArray(); + for (int i = 0; i < c.length; i++) { + // call draw on each char (pulling it w/ the unicode table) + FontGlyph fg = unicodeGlyphs.get(Character.valueOf(c[i])); + if (fg != null) { + fg.draw(g); + // add horizAdvX/unitsPerEm to the x coordinate along the way + g.translate(fg.horizAdvX, 0); + } else { + System.err.println("'" + c[i] + "' not available."); + } + } + g.popMatrix(); + } + + + public void drawChar(PGraphics g, char c, float x, float y, float size) { + g.pushMatrix(); + float s = size / face.unitsPerEm; + g.translate(x, y); + g.scale(s, -s); + FontGlyph fg = unicodeGlyphs.get(Character.valueOf(c)); + if (fg != null) g.shape(fg); + g.popMatrix(); + } + + + public float textWidth(String str, float size) { + float w = 0; + char[] c = str.toCharArray(); + for (int i = 0; i < c.length; i++) { + // call draw on each char (pulling it w/ the unicode table) + FontGlyph fg = unicodeGlyphs.get(Character.valueOf(c[i])); + if (fg != null) { + w += (float) fg.horizAdvX / face.unitsPerEm; + } + } + return w * size; + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static class FontFace extends PShapeSVG { + int horizOriginX; // dflt 0 + int horizOriginY; // dflt 0 + // int horizAdvX; // no dflt? + int vertOriginX; // dflt horizAdvX/2 + int vertOriginY; // dflt ascent + int vertAdvY; // dflt 1em (unitsPerEm value) + + String fontFamily; + int fontWeight; // can also be normal or bold (also comma separated) + String fontStretch; + int unitsPerEm; // dflt 1000 + int[] panose1; // dflt "0 0 0 0 0 0 0 0 0 0" + int ascent; + int descent; + int[] bbox; // spec says comma separated, tho not w/ forge + int underlineThickness; + int underlinePosition; + //String unicodeRange; // gonna ignore for now + + + public FontFace(PShapeSVG parent, XML properties) { + super(parent, properties, true); + + unitsPerEm = properties.getInt("units-per-em", 1000); + } + + + protected void drawShape() { + // nothing to draw in the font face attribute + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static public class FontGlyph extends PShapeSVG { // extends Path + public String name; + char unicode; + int horizAdvX; + + public FontGlyph(PShapeSVG parent, XML properties, Font font) { + super(parent, properties, true); + super.parsePath(); // ?? + + name = properties.getString("glyph-name"); + String u = properties.getString("unicode"); + unicode = 0; + if (u != null) { + if (u.length() == 1) { + unicode = u.charAt(0); + //System.out.println("unicode for " + name + " is " + u); + } else { + System.err.println("unicode for " + name + + " is more than one char: " + u); + } + } + if (properties.hasAttribute("horiz-adv-x")) { + horizAdvX = properties.getInt("horiz-adv-x"); + } else { + horizAdvX = font.horizAdvX; + } + } + + + protected boolean isLegit() { // TODO need a better way to handle this... + return vertexCount != 0; + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** + * Get a particular element based on its SVG ID. When editing SVG by hand, + * this is the id="" tag on any SVG element. When editing from Illustrator, + * these IDs can be edited by expanding the layers palette. The names used + * in the layers palette, both for the layers or the shapes and groups + * beneath them can be used here. + *
    +   * // This code grabs "Layer 3" and the shapes beneath it.
    +   * PShape layer3 = svg.getChild("Layer 3");
    +   * 
    + */ + @Override + public PShape getChild(String name) { + PShape found = super.getChild(name); + if (found == null) { + // Otherwise try with underscores instead of spaces + // (this is how Illustrator handles spaces in the layer names). + found = super.getChild(name.replace(' ', '_')); + } + // Set bounding box based on the parent bounding box + if (found != null) { +// found.x = this.x; +// found.y = this.y; + found.width = this.width; + found.height = this.height; + } + return found; + } + + + /** + * Prints out the SVG document. Useful for parsing. + */ + public void print() { + PApplet.println(element.toString()); + } +} diff --git a/core/src/processing/core/PStyle.java b/libs/processing-core/src/main/java/processing/core/PStyle.java similarity index 91% rename from core/src/processing/core/PStyle.java rename to libs/processing-core/src/main/java/processing/core/PStyle.java index f7f388950..f5e1ecffb 100644 --- a/core/src/processing/core/PStyle.java +++ b/libs/processing-core/src/main/java/processing/core/PStyle.java @@ -3,7 +3,9 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2008-10 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2006-12 Ben Fry and Casey Reas + Copyright (c) 2004-06 Michael Chang This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -29,6 +31,8 @@ public class PStyle implements PConstants { public int ellipseMode; public int shapeMode; + public int blendMode; + public int colorMode; public float colorModeX; public float colorModeY; diff --git a/libs/processing-core/src/main/java/processing/core/PSurface.java b/libs/processing-core/src/main/java/processing/core/PSurface.java new file mode 100644 index 000000000..4a2ec763b --- /dev/null +++ b/libs/processing-core/src/main/java/processing/core/PSurface.java @@ -0,0 +1,108 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.AssetManager; +import android.graphics.Rect; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; + +import java.io.File; +import java.io.InputStream; + +import processing.android.AppComponent; +import processing.android.ServiceEngine; + +/* + * Holds the surface view associated with the sketch, and the rendering thread + * handling + */ +public interface PSurface { + public static final int REQUEST_PERMISSIONS = 1; + + public AppComponent getComponent(); + public Context getContext(); + public Activity getActivity(); + public ServiceEngine getEngine(); + + public void dispose(); + + public String getName(); + + public View getResource(int id); + + public Rect getVisibleFrame(); + + public SurfaceView getSurfaceView(); + public SurfaceHolder getSurfaceHolder(); + + public View getRootView(); + public void setRootView(View view); + + public void initView(int sketchWidth, int sketchHeight); + public void initView(int sketchWidth, int sketchHeight, boolean parentSize, + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState); + + public void startActivity(Intent intent); + + public void runOnUiThread(Runnable action); + + public void setOrientation(int which); + + public void setHasOptionsMenu(boolean hasMenu); + + public File getFilesDir(); + + public File getFileStreamPath(String path); + + public InputStream openFileInput(String filename); + + public AssetManager getAssets(); + + public void setSystemUiVisibility(int visibility); + + public void startThread(); + + public void pauseThread(); + + public void resumeThread(); + + public boolean stopThread(); + + public boolean isStopped(); + + public void finish(); + + public void setFrameRate(float fps); + + public boolean hasPermission(String permission); + public void requestPermissions(String[] permissions); +} diff --git a/libs/processing-core/src/main/java/processing/core/PSurfaceNone.java b/libs/processing-core/src/main/java/processing/core/PSurfaceNone.java new file mode 100644 index 000000000..5858ed1f7 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/core/PSurfaceNone.java @@ -0,0 +1,595 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.core; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.graphics.Rect; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.LayoutInflater; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.os.ResultReceiver; + +import android.service.wallpaper.WallpaperService; +import android.support.wearable.watchface.WatchFaceService; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import processing.android.AppComponent; +import processing.android.PFragment; +import processing.android.ServiceEngine; +import processing.android.PermissionRequestor; + +/** + * Base surface for Android2D and OpenGL renderers. + * It includes the implementation of the rendering thread. + */ +public class PSurfaceNone implements PSurface, PConstants { + protected PApplet sketch; + protected PGraphics graphics; + protected AppComponent component; + protected Activity activity; + + protected boolean surfaceReady; + protected SurfaceView surfaceView; + protected View view; + + protected WallpaperService wallpaper; + protected WatchFaceService watchface; + + protected boolean requestedThreadStart = false; + protected Thread thread; + protected boolean paused; + protected final Object pauseObject = new Object(); + + protected float frameRateTarget = 60; + protected long frameRatePeriod = 1000000000L / 60L; + + + @Override + public AppComponent getComponent() { + return component; + } + + + @Override + public Context getContext() { + if (component.getKind() == AppComponent.FRAGMENT) { + return activity; + } else if (component.getKind() == AppComponent.WALLPAPER) { + return wallpaper; + } else if (component.getKind() == AppComponent.WATCHFACE) { + return watchface; + } + return null; + } + + + @Override + public Activity getActivity() { + return activity; + } + + + @Override + public ServiceEngine getEngine() { + return component.getEngine(); + } + + + @Override + public View getRootView() { + return view; + } + + + @Override + public String getName() { + if (component.getKind() == AppComponent.FRAGMENT) { + return activity.getComponentName().getPackageName(); + } else if (component.getKind() == AppComponent.WALLPAPER) { + return wallpaper.getPackageName(); + } else if (component.getKind() == AppComponent.WATCHFACE) { + return watchface.getPackageName(); + } + return ""; + } + + + @Override + public View getResource(int id) { + return activity.findViewById(id); + } + + + @Override + public Rect getVisibleFrame() { + Rect frame = new Rect(); + if (view != null) { + // According to the docs: + // https://developer.android.com/reference/android/view/View.html#getWindowVisibleDisplayFrame(android.graphics.Rect) + // don't use in performance critical code like drawing. + view.getWindowVisibleDisplayFrame(frame); + } + return frame; + } + + + @Override + public void dispose() { + sketch = null; + graphics = null; + + if (activity != null) { + // In API level 21 you can do + // activity.releaseInstance(); + // to ask the app to free up its memory. + // https://developer.android.com/reference/android/app/Activity.html#releaseInstance() + // but seems redundant to call it here, since dispose() is triggered by + // the onDestroy() handler, which means that the app is already + // being destroyed. + } + + if (view != null) { + view.destroyDrawingCache(); + } + + if (component != null) { + component.dispose(); + } + + if (surfaceView != null) { + surfaceView.getHolder().getSurface().release(); + } + } + + + @Override + public void setRootView(View view) { + this.view = view; + } + + + @Override + public SurfaceView getSurfaceView() { + return surfaceView; + } + + + @Override + // TODO this is only used by A2D, when finishing up a draw. but if the + // surfaceview has changed, then it might belong to an a3d surfaceview. hrm. + public SurfaceHolder getSurfaceHolder() { + SurfaceView view = getSurfaceView(); + if (view == null) { + // Watch faces don't have a surface view associated to them. + return null; + } else { + return view.getHolder(); + } + } + + + @Override + public void initView(int sketchWidth, int sketchHeight) { + if (component.getKind() == AppComponent.FRAGMENT) { + int displayWidth = component.getDisplayWidth(); + int displayHeight = component.getDisplayHeight(); + View rootView; + if (sketchWidth == displayWidth && sketchHeight == displayHeight) { + rootView = getSurfaceView(); + } else { + RelativeLayout overallLayout = new RelativeLayout(activity); + RelativeLayout.LayoutParams lp = + new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + lp.addRule(RelativeLayout.CENTER_IN_PARENT); + + LinearLayout layout = new LinearLayout(activity); + layout.addView(getSurfaceView(), sketchWidth, sketchHeight); + overallLayout.addView(layout, lp); + overallLayout.setBackgroundColor(sketch.sketchWindowColor()); + rootView = overallLayout; + } + setRootView(rootView); + } else if (component.getKind() == AppComponent.WALLPAPER) { + setRootView(getSurfaceView()); + } + } + + + @Override + public void initView(int sketchWidth, int sketchHeight, boolean parentSize, + LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // https://www.bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/ + ViewGroup rootView = (ViewGroup)inflater.inflate(sketch.parentLayout, container, false); + + View view = getSurfaceView(); + if (parentSize) { + LinearLayout.LayoutParams lp; + lp = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT); + lp.weight = 1.0f; + lp.setMargins(0, 0, 0, 0); + view.setPadding(0,0,0,0); + rootView.addView(view, lp); + } else { + RelativeLayout layout = new RelativeLayout(activity); + RelativeLayout.LayoutParams lp = + new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + lp.addRule(RelativeLayout.CENTER_IN_PARENT); + + layout.addView(view, sketchWidth, sketchHeight); + rootView.addView(layout, lp); + } + rootView.setBackgroundColor(sketch.sketchWindowColor()); + setRootView(rootView); + } + + + @Override + public void startActivity(Intent intent) { + component.startActivity(intent); + } + + + @Override + public void runOnUiThread(Runnable action) { + if (component.getKind() == AppComponent.FRAGMENT) { + activity.runOnUiThread(action); + } + } + + + @Override + public void setOrientation(int which) { + if (component.getKind() == AppComponent.FRAGMENT) { + if (which == PORTRAIT) { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } else if (which == LANDSCAPE) { + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } + } + } + + + public void setHasOptionsMenu(boolean hasMenu) { + if (component.getKind() == AppComponent.FRAGMENT) { + ((PFragment)component).setHasOptionsMenu(hasMenu); + } + } + + + + @Override + public File getFilesDir() { + if (component.getKind() == AppComponent.FRAGMENT) { + return activity.getFilesDir(); + } else if (component.getKind() == AppComponent.WALLPAPER) { + return wallpaper.getFilesDir(); + } else if (component.getKind() == AppComponent.WATCHFACE) { + return watchface.getFilesDir(); + } + return null; + } + + + @Override + public File getFileStreamPath(String path) { + if (component.getKind() == AppComponent.FRAGMENT) { + return activity.getFileStreamPath(path); + } else if (component.getKind() == AppComponent.WALLPAPER) { + return wallpaper.getFileStreamPath(path); + } else if (component.getKind() == AppComponent.WATCHFACE) { + return watchface.getFileStreamPath(path); + } + return null; + } + + + @Override + public InputStream openFileInput(String filename) { + if (component.getKind() == AppComponent.FRAGMENT) { + try { + return activity.openFileInput(filename); + } catch (FileNotFoundException e) { + System.err.println("Cannot open file " + filename); + } + } + return null; + } + + + @Override + public AssetManager getAssets() { + if (component.getKind() == AppComponent.FRAGMENT) { + return activity.getAssets(); + } else if (component.getKind() == AppComponent.WALLPAPER) { + return wallpaper.getBaseContext().getAssets(); + } else if (component.getKind() == AppComponent.WATCHFACE) { + return watchface.getBaseContext().getAssets(); + } + return null; + } + + + @Override + public void setSystemUiVisibility(int visibility) { + int kind = component.getKind(); + if (kind == AppComponent.FRAGMENT || kind == AppComponent.WALLPAPER) { + surfaceView.setSystemUiVisibility(visibility); + } + } + + + @Override + public void finish() { + if (component == null) return; + + if (component.getKind() == AppComponent.FRAGMENT) { + // This is the correct way to stop the sketch programmatically, according to the developer's docs: + // https://developer.android.com/reference/android/app/Activity.html#onDestroy() + // https://developer.android.com/reference/android/app/Activity.html#finish() + // and online discussions: + // http://stackoverflow.com/questions/2033914/quitting-an-application-is-that-frowned-upon/2034238 + // finish() it will trigger an onDestroy() event, which will translate down through the + // activity hierarchy and eventually pausing and stopping Processing's animation thread, etc. + activity.finish(); + } else if (component.getKind() == AppComponent.WALLPAPER) { + // stopSelf() stops a service from within: + // https://developer.android.com/reference/android/app/Service.html#stopSelf() + wallpaper.stopSelf(); + } else if (component.getKind() == AppComponent.WATCHFACE) { + watchface.stopSelf(); + } + } + + /////////////////////////////////////////////////////////// + + // Thread handling + + + public Thread createThread() { + return new AnimationThread(); + } + + + @Override + public void startThread() { + if (!surfaceReady) { + requestedThreadStart = true; + return; + } + + if (thread == null) { + thread = createThread(); + thread.start(); + requestedThreadStart = false; + } else { + throw new IllegalStateException("Thread already started in " + + getClass().getSimpleName()); + } + } + + + @Override + public void pauseThread() { + if (!surfaceReady) return; + + paused = true; + } + + + @Override + public void resumeThread() { + if (!surfaceReady) return; + + if (thread == null) { + thread = createThread(); + thread.start(); + } + + paused = false; + synchronized (pauseObject) { + pauseObject.notifyAll(); // wake up the animation thread + } + } + + + @Override + public boolean stopThread() { + if (!surfaceReady) return true; + + if (thread == null) { + return false; + } + + thread.interrupt(); + thread = null; + + return true; + } + + + @Override + public boolean isStopped() { + return thread == null; + } + + + public void setFrameRate(float fps) { + frameRateTarget = fps; + frameRatePeriod = (long) (1000000000.0 / frameRateTarget); + } + + + protected void checkPause() throws InterruptedException { + synchronized (pauseObject) { + while (paused) { + pauseObject.wait(); + } + } + } + + + protected void callDraw() { + component.requestDraw(); + if (component.canDraw() && sketch != null) { + sketch.handleDraw(); + } + } + + + public class AnimationThread extends Thread { + public AnimationThread() { + super("Animation Thread"); + } + + /** + * Main method for the primary animation thread. + * Painting in AWT and Swing + */ + @Override + public void run() { // not good to make this synchronized, locks things up + long beforeTime = System.nanoTime(); + long overSleepTime = 0L; + + int noDelays = 0; + // Number of frames with a delay of 0 ms before the + // animation thread yields to other running threads. + final int NO_DELAYS_PER_YIELD = 15; + + if (sketch == null) return; + + // un-pause the sketch and get rolling + sketch.start(); + + while ((Thread.currentThread() == thread) && + (sketch != null && !sketch.finished)) { + if (Thread.currentThread().isInterrupted()) { + return; + } + try { + checkPause(); + } catch (InterruptedException e) { + return; + } + + callDraw(); + + // wait for update & paint to happen before drawing next frame + // this is necessary since the drawing is sometimes in a + // separate thread, meaning that the next frame will start + // before the update/paint is completed + + long afterTime = System.nanoTime(); + long timeDiff = afterTime - beforeTime; + //System.out.println("time diff is " + timeDiff); + long sleepTime = (frameRatePeriod - timeDiff) - overSleepTime; + + if (sleepTime > 0) { // some time left in this cycle + try { + Thread.sleep(sleepTime / 1000000L, (int) (sleepTime % 1000000L)); + noDelays = 0; // Got some sleep, not delaying anymore + } catch (InterruptedException ex) { + System.err.println("Cannot properly set the timing for the draw animation."); + } + + overSleepTime = (System.nanoTime() - afterTime) - sleepTime; + + } else { // sleepTime <= 0; the frame took longer than the period + overSleepTime = 0L; + noDelays++; + + if (noDelays > NO_DELAYS_PER_YIELD) { + Thread.yield(); // give another thread a chance to run + noDelays = 0; + } + } + + beforeTime = System.nanoTime(); + } + } + } + + + public boolean hasPermission(String permission) { + int res = ContextCompat.checkSelfPermission(getContext(), permission); + return res == PackageManager.PERMISSION_GRANTED; + } + + + public void requestPermissions(String[] permissions) { + if (component.isService()) { + // https://developer.android.com/training/articles/wear-permissions.html + // Inspired by PermissionHelper.java from Michael von Glasow: + // https://github.com/mvglasow/satstat/blob/master/src/com/vonglasow/michael/satstat/utils/PermissionHelper.java + // Example of use: + // https://github.com/mvglasow/satstat/blob/master/src/com/vonglasow/michael/satstat/PasvLocListenerService.java + final ServiceEngine eng = getEngine(); + if (eng != null) { // A valid service should have a non-null engine at this point, but just in case + ResultReceiver resultReceiver = new ResultReceiver(new Handler(Looper.getMainLooper())) { + @Override + protected void onReceiveResult (int resultCode, Bundle resultData) { + String[] outPermissions = resultData.getStringArray(PermissionRequestor.KEY_PERMISSIONS); + int[] grantResults = resultData.getIntArray(PermissionRequestor.KEY_GRANT_RESULTS); + eng.onRequestPermissionsResult(resultCode, outPermissions, grantResults); + } + }; + final Intent permIntent = new Intent(getContext(), PermissionRequestor.class); + permIntent.putExtra(PermissionRequestor.KEY_RESULT_RECEIVER, resultReceiver); + permIntent.putExtra(PermissionRequestor.KEY_PERMISSIONS, permissions); + permIntent.putExtra(PermissionRequestor.KEY_REQUEST_CODE, REQUEST_PERMISSIONS); + // Show the dialog requesting the permissions + permIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(permIntent); + } + } else if (activity != null) { + // Requesting permissions from user when the app resumes. + // Nice example on how to handle user response + // http://stackoverflow.com/a/35495855 + // More on permission in Android 23: + // https://inthecheesefactory.com/blog/things-you-need-to-know-about-android-m-permission-developer-edition/en + ActivityCompat.requestPermissions(activity, permissions, REQUEST_PERMISSIONS); + } + } +} diff --git a/core/src/processing/core/PVector.java b/libs/processing-core/src/main/java/processing/core/PVector.java similarity index 90% rename from core/src/processing/core/PVector.java rename to libs/processing-core/src/main/java/processing/core/PVector.java index 725c208b1..d1c074f92 100644 --- a/core/src/processing/core/PVector.java +++ b/libs/processing-core/src/main/java/processing/core/PVector.java @@ -3,8 +3,10 @@ /* Part of the Processing project - http://processing.org + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2008-12 Ben Fry and Casey Reas Copyright (c) 2008 Dan Shiffman - Copyright (c) 2008-10 Ben Fry and Casey Reas + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -68,12 +70,6 @@ * @webref math */ public class PVector implements Serializable { - - /** - * Generated 2010-09-14 by jdf - */ - private static final long serialVersionUID = -6717872085945400694L; - /** * ( begin auto-generated from PVector_x.xml ) * @@ -119,6 +115,7 @@ public class PVector implements Serializable { /** Array so that this can be temporarily used in an array context */ transient protected float[] array; + /** * Constructor for an empty vector: x, y, and z are set to 0. */ @@ -149,6 +146,7 @@ public PVector(float x, float y) { this.z = 0; } + /** * ( begin auto-generated from PVector_set.xml ) * @@ -161,33 +159,35 @@ public PVector(float x, float y) { * @param x the x component of the vector * @param y the y component of the vector * @param z the z component of the vector - * @brief Set the x, y, and z component of the vector + * @brief Set the components of the vector */ - public void set(float x, float y, float z) { + public PVector set(float x, float y, float z) { this.x = x; this.y = y; this.z = z; + return this; } + /** - * - * @webref pvector:method * @param x the x component of the vector * @param y the y component of the vector - * @brief Set the x, y components of the vector */ - public void set(float x, float y) { + public PVector set(float x, float y) { this.x = x; this.y = y; + return this; } + /** * @param v any variable of type PVector */ - public void set(PVector v) { + public PVector set(PVector v) { x = v.x; y = v.y; z = v.z; + return this; } @@ -195,7 +195,7 @@ public void set(PVector v) { * Set the x, y (and maybe z) coordinates using a float[] array as the source. * @param source array to copy from */ - public void set(float[] source) { + public PVector set(float[] source) { if (source.length >= 2) { x = source[0]; y = source[1]; @@ -203,6 +203,7 @@ public void set(float[] source) { if (source.length >= 3) { z = source[2]; } + return this; } @@ -220,9 +221,10 @@ public void set(float[] source) { * @see PVector#random3D() */ static public PVector random2D() { - return random2D(null,null); + return random2D(null, null); } + /** * Make a new 2D unit vector with a random direction * using Processing's current random number generator @@ -230,7 +232,7 @@ static public PVector random2D() { * @return the random PVector */ static public PVector random2D(PApplet parent) { - return random2D(null,parent); + return random2D(null, parent); } /** @@ -239,18 +241,23 @@ static public PVector random2D(PApplet parent) { * @return the random PVector */ static public PVector random2D(PVector target) { - return random2D(target,null); + return random2D(target, null); } + /** - * Make a new 2D unit vector with a random direction + * Make a new 2D unit vector with a random direction. Pass in the parent + * PApplet if you want randomSeed() to work (and be predictable). Or leave + * it null and be... random. * @return the random PVector */ static public PVector random2D(PVector target, PApplet parent) { - if (parent == null) return fromAngle((float)(Math.random()*Math.PI*2),target); - else return fromAngle(parent.random(PConstants.TWO_PI),target); + return (parent == null) ? + fromAngle((float) (Math.random() * Math.PI*2), target) : + fromAngle(parent.random(PConstants.TAU), target); } + /** * ( begin auto-generated from PVector_random3D.xml ) * @@ -265,9 +272,10 @@ static public PVector random2D(PVector target, PApplet parent) { * @see PVector#random2D() */ static public PVector random3D() { - return random3D(null,null); + return random3D(null, null); } + /** * Make a new 3D unit vector with a random direction * using Processing's current random number generator @@ -275,18 +283,20 @@ static public PVector random3D() { * @return the random PVector */ static public PVector random3D(PApplet parent) { - return random3D(null,parent); + return random3D(null, parent); } + /** * Set a 3D vector to a random unit vector with a random direction * @param target the target vector (if null, a new vector will be created) * @return the random PVector */ static public PVector random3D(PVector target) { - return random3D(target,null); + return random3D(target, null); } + /** * Make a new 3D unit vector with a random direction * @return the random PVector @@ -312,6 +322,7 @@ static public PVector random3D(PVector target, PApplet parent) { return target; } + /** * ( begin auto-generated from PVector_sub.xml ) * @@ -322,7 +333,7 @@ static public PVector random3D(PVector target, PApplet parent) { * @webref pvector:method * @usage web_application * @brief Make a new 2D unit vector from an angle - * @param angle the angle + * @param angle the angle in radians * @return the new unit PVector */ static public PVector fromAngle(float angle) { @@ -345,8 +356,9 @@ static public PVector fromAngle(float angle, PVector target) { return target; } + /** - * ( begin auto-generated from PVector_get.xml ) + * ( begin auto-generated from PVector_copy.xml ) * * Gets a copy of the vector, returns a PVector object. * @@ -356,10 +368,17 @@ static public PVector fromAngle(float angle, PVector target) { * @usage web_application * @brief Get a copy of the vector */ - public PVector get() { + public PVector copy() { return new PVector(x, y, z); } + + @Deprecated + public PVector get() { + return copy(); + } + + /** * @param target */ @@ -396,6 +415,7 @@ public float mag() { return (float) Math.sqrt(x*x + y*y + z*z); } + /** * ( begin auto-generated from PVector_mag.xml ) * @@ -416,6 +436,7 @@ public float magSq() { return (x*x + y*y + z*z); } + /** * ( begin auto-generated from PVector_add.xml ) * @@ -432,21 +453,33 @@ public float magSq() { * @param v the vector to be added * @brief Adds x, y, and z components to a vector, one vector to another, or two independent vectors */ - public void add(PVector v) { + public PVector add(PVector v) { x += v.x; y += v.y; z += v.z; + return this; } + /** * @param x x component of the vector * @param y y component of the vector + */ + public PVector add(float x, float y) { + this.x += x; + this.y += y; + return this; + } + + + /** * @param z z component of the vector */ - public void add(float x, float y, float z) { + public PVector add(float x, float y, float z) { this.x += x; this.y += y; this.z += z; + return this; } @@ -490,21 +523,33 @@ static public PVector add(PVector v1, PVector v2, PVector target) { * @param v any variable of type PVector * @brief Subtract x, y, and z components from a vector, one vector from another, or two independent vectors */ - public void sub(PVector v) { + public PVector sub(PVector v) { x -= v.x; y -= v.y; z -= v.z; + return this; } + /** * @param x the x component of the vector * @param y the y component of the vector + */ + public PVector sub(float x, float y) { + this.x -= x; + this.y -= y; + return this; + } + + + /** * @param z the z component of the vector */ - public void sub(float x, float y, float z) { + public PVector sub(float x, float y, float z) { this.x -= x; this.y -= y; this.z -= z; + return this; } @@ -517,10 +562,9 @@ static public PVector sub(PVector v1, PVector v2) { return sub(v1, v2, null); } + /** * Subtract one vector from another and store in another vector - * @param v1 the x, y, and z components of a PVector object - * @param v2 the x, y, and z components of a PVector object * @param target PVector in which to store the result */ static public PVector sub(PVector v1, PVector v2, PVector target) { @@ -545,10 +589,11 @@ static public PVector sub(PVector v1, PVector v2, PVector target) { * @brief Multiply a vector by a scalar * @param n the number to multiply with the vector */ - public void mult(float n) { + public PVector mult(float n) { x *= n; y *= n; z *= n; + return this; } @@ -574,7 +619,6 @@ static public PVector mult(PVector v, float n, PVector target) { } - /** * ( begin auto-generated from PVector_div.xml ) * @@ -587,10 +631,11 @@ static public PVector mult(PVector v, float n, PVector target) { * @brief Divide a vector by a scalar * @param n the number by which to divide the vector */ - public void div(float n) { + public PVector div(float n) { x /= n; y /= n; z /= n; + return this; } @@ -603,6 +648,7 @@ static public PVector div(PVector v, float n) { return div(v, n, null); } + /** * Divide a vector by a scalar and store the result in another vector. * @param target PVector in which to store the result @@ -668,6 +714,7 @@ public float dot(PVector v) { return x*v.x + y*v.y + z*v.z; } + /** * @param x x component of the vector * @param y y component of the vector @@ -677,6 +724,7 @@ public float dot(float x, float y, float z) { return this.x*x + this.y*y + this.z*z; } + /** * @param v1 any variable of type PVector * @param v2 any variable of type PVector @@ -720,6 +768,7 @@ public PVector cross(PVector v, PVector target) { return target; } + /** * @param v1 any variable of type PVector * @param v2 any variable of type PVector @@ -750,11 +799,12 @@ static public PVector cross(PVector v1, PVector v2, PVector target) { * @usage web_application * @brief Normalize the vector to a length of 1 */ - public void normalize() { + public PVector normalize() { float m = mag(); if (m != 0 && m != 1) { div(m); } + return this; } @@ -788,13 +838,15 @@ public PVector normalize(PVector target) { * @param max the maximum magnitude for the vector * @brief Limit the magnitude of the vector */ - public void limit(float max) { + public PVector limit(float max) { if (magSq() > max*max) { normalize(); mult(max); } + return this; } + /** * ( begin auto-generated from PVector_setMag.xml ) * @@ -807,11 +859,13 @@ public void limit(float max) { * @param len the new length for this vector * @brief Set the magnitude of the vector */ - public void setMag(float len) { + public PVector setMag(float len) { normalize(); mult(len); + return this; } + /** * Sets the magnitude of this vector, storing the result in another vector. * @param target Set to null to create a new vector @@ -824,6 +878,7 @@ public PVector setMag(PVector target, float len) { return target; } + /** * ( begin auto-generated from PVector_setMag.xml ) * @@ -837,8 +892,8 @@ public PVector setMag(PVector target, float len) { * @brief Calculate the angle of rotation for this vector */ public float heading() { - float angle = (float) Math.atan2(-y, x); - return -1*angle; + float angle = (float) Math.atan2(y, x); + return angle; } @@ -860,11 +915,12 @@ public float heading2D() { * @brief Rotate the vector by an angle (2D only) * @param theta the angle of rotation */ - public void rotate(float theta) { - float xTemp = x; + public PVector rotate(float theta) { + float temp = x; // Might need to check for rounding errors like with angleBetween function? x = x*PApplet.cos(theta) - y*PApplet.sin(theta); - y = xTemp*PApplet.sin(theta) + y*PApplet.cos(theta); + y = temp*PApplet.sin(theta) + y*PApplet.cos(theta); + return this; } @@ -879,37 +935,43 @@ public void rotate(float theta) { * @usage web_application * @brief Linear interpolate the vector to another vector * @param v the vector to lerp to - * @param amt The amount of interpolation; some value between 0.0 (old vector) and 1.0 (new vector). 0.1 is very near the new vector. 0.5 is halfway in between. + * @param amt The amount of interpolation; some value between 0.0 (old vector) and 1.0 (new vector). 0.1 is very near the old vector; 0.5 is halfway in between. + * @see PApplet#lerp(float, float, float) */ - public void lerp(PVector v, float amt) { - x = PApplet.lerp(x,v.x,amt); - y = PApplet.lerp(y,v.y,amt); - z = PApplet.lerp(z,v.z,amt); + public PVector lerp(PVector v, float amt) { + x = PApplet.lerp(x, v.x, amt); + y = PApplet.lerp(y, v.y, amt); + z = PApplet.lerp(z, v.z, amt); + return this; } + /** * Linear interpolate between two vectors (returns a new PVector object) * @param v1 the vector to start from * @param v2 the vector to lerp to */ public static PVector lerp(PVector v1, PVector v2, float amt) { - PVector v = v1.get(); + PVector v = v1.copy(); v.lerp(v2, amt); return v; } + /** * Linear interpolate the vector to x,y,z values * @param x the x component to lerp to * @param y the y component to lerp to * @param z the z component to lerp to */ - public void lerp(float x, float y, float z, float amt) { - this.x = PApplet.lerp(this.x,x,amt); - this.y = PApplet.lerp(this.y,y,amt); - this.z = PApplet.lerp(this.z,z,amt); + public PVector lerp(float x, float y, float z, float amt) { + this.x = PApplet.lerp(this.x, x, amt); + this.y = PApplet.lerp(this.y, y, amt); + this.z = PApplet.lerp(this.z, z, amt); + return this; } + /** * ( begin auto-generated from PVector_angleBetween.xml ) * @@ -926,9 +988,9 @@ public void lerp(float x, float y, float z, float amt) { static public float angleBetween(PVector v1, PVector v2) { // We get NaN if we pass in a zero vector which can cause problems - // Zero seems like a reasonable angle between a (0,0) vector and something else - if (v1.x == 0 && v1.y == 0) return 0.0f; - if (v2.x == 0 && v2.y == 0) return 0.0f; + // Zero seems like a reasonable angle between a (0,0,0) vector and something else + if (v1.x == 0 && v1.y == 0 && v1.z == 0 ) return 0.0f; + if (v2.x == 0 && v2.y == 0 && v2.z == 0 ) return 0.0f; double dot = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z; double v1mag = Math.sqrt(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z); @@ -978,14 +1040,17 @@ public float[] array() { return array; } + @Override public boolean equals(Object obj) { - if (!(obj instanceof PVector)) + if (!(obj instanceof PVector)) { return false; + } final PVector p = (PVector) obj; return x == p.x && y == p.y && z == p.z; } + @Override public int hashCode() { int result = 1; @@ -994,4 +1059,4 @@ public int hashCode() { result = 31 * result + Float.floatToIntBits(z); return result; } -} \ No newline at end of file +} diff --git a/libs/processing-core/src/main/java/processing/data/DoubleDict.java b/libs/processing-core/src/main/java/processing/data/DoubleDict.java new file mode 100644 index 000000000..f2a9adf10 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/data/DoubleDict.java @@ -0,0 +1,850 @@ +package processing.data; + +import java.io.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; + +import processing.core.PApplet; + + +/** + * A simple table class to use a String as a lookup for an double value. + * + * @webref data:composite + * @see IntDict + * @see StringDict + */ +public class DoubleDict { + + /** Number of elements in the table */ + protected int count; + + protected String[] keys; + protected double[] values; + + /** Internal implementation for faster lookups */ + private HashMap indices = new HashMap<>(); + + + public DoubleDict() { + count = 0; + keys = new String[10]; + values = new double[10]; + } + + + /** + * Create a new lookup with a specific size. This is more efficient than not + * specifying a size. Use it when you know the rough size of the thing you're creating. + * + * @nowebref + */ + public DoubleDict(int length) { + count = 0; + keys = new String[length]; + values = new double[length]; + } + + + /** + * Read a set of entries from a Reader that has each key/value pair on + * a single line, separated by a tab. + * + * @nowebref + */ + public DoubleDict(BufferedReader reader) { + String[] lines = PApplet.loadStrings(reader); + keys = new String[lines.length]; + values = new double[lines.length]; + + for (int i = 0; i < lines.length; i++) { + String[] pieces = PApplet.split(lines[i], '\t'); + if (pieces.length == 2) { + keys[count] = pieces[0]; + values[count] = PApplet.parseFloat(pieces[1]); + indices.put(pieces[0], count); + count++; + } + } + } + + + /** + * @nowebref + */ + public DoubleDict(String[] keys, double[] values) { + if (keys.length != values.length) { + throw new IllegalArgumentException("key and value arrays must be the same length"); + } + this.keys = keys; + this.values = values; + count = keys.length; + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + /** + * Constructor to allow (more intuitive) inline initialization, e.g.: + *
    +   * new FloatDict(new Object[][] {
    +   *   { "key1", 1 },
    +   *   { "key2", 2 }
    +   * });
    +   * 
    + */ + public DoubleDict(Object[][] pairs) { + count = pairs.length; + this.keys = new String[count]; + this.values = new double[count]; + for (int i = 0; i < count; i++) { + keys[i] = (String) pairs[i][0]; + values[i] = (Float) pairs[i][1]; + indices.put(keys[i], i); + } + } + + + public DoubleDict(Map incoming) { + count = incoming.size(); + keys = new String[count]; + values = new double[count]; + int index = 0; + for (Map.Entry e : incoming.entrySet()) { + keys[index] = e.getKey(); + values[index] = e.getValue(); + indices.put(keys[index], index); + index++; + } + } + + + /** + * @webref doubledict:method + * @brief Returns the number of key/value pairs + */ + public int size() { + return count; + } + + + /** + * Resize the internal data, this can only be used to shrink the list. + * Helpful for situations like sorting and then grabbing the top 50 entries. + */ + public void resize(int length) { + if (length == count) return; + + if (length > count) { + throw new IllegalArgumentException("resize() can only be used to shrink the dictionary"); + } + if (length < 1) { + throw new IllegalArgumentException("resize(" + length + ") is too small, use 1 or higher"); + } + + String[] newKeys = new String[length]; + double[] newValues = new double[length]; + PApplet.arrayCopy(keys, newKeys, length); + PApplet.arrayCopy(values, newValues, length); + keys = newKeys; + values = newValues; + count = length; + resetIndices(); + } + + + /** + * Remove all entries. + * + * @webref doubledict:method + * @brief Remove all entries + */ + public void clear() { + count = 0; + indices = new HashMap<>(); + } + + + private void resetIndices() { + indices = new HashMap<>(count); + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public class Entry { + public String key; + public double value; + + Entry(String key, double value) { + this.key = key; + this.value = value; + } + } + + + public Iterable entries() { + return new Iterable() { + + public Iterator iterator() { + return entryIterator(); + } + }; + } + + + public Iterator entryIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Entry next() { + ++index; + Entry e = new Entry(keys[index], values[index]); + return e; + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public String key(int index) { + return keys[index]; + } + + + protected void crop() { + if (count != keys.length) { + keys = PApplet.subset(keys, 0, count); + values = PApplet.subset(values, 0, count); + } + } + + + public Iterable keys() { + return new Iterable() { + + @Override + public Iterator iterator() { + return keyIterator(); + } + }; + } + + + // Use this to iterate when you want to be able to remove elements along the way + public Iterator keyIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public String next() { + return key(++index); + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + /** + * Return a copy of the internal keys array. This array can be modified. + * + * @webref doubledict:method + * @brief Return a copy of the internal keys array + */ + public String[] keyArray() { + crop(); + return keyArray(null); + } + + + public String[] keyArray(String[] outgoing) { + if (outgoing == null || outgoing.length != count) { + outgoing = new String[count]; + } + System.arraycopy(keys, 0, outgoing, 0, count); + return outgoing; + } + + + public double value(int index) { + return values[index]; + } + + + /** + * @webref doubledict:method + * @brief Return the internal array being used to store the values + */ + public Iterable values() { + return new Iterable() { + + @Override + public Iterator iterator() { + return valueIterator(); + } + }; + } + + + public Iterator valueIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Double next() { + return value(++index); + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + /** + * Create a new array and copy each of the values into it. + * + * @webref doubledict:method + * @brief Create a new array and copy each of the values into it + */ + public double[] valueArray() { + crop(); + return valueArray(null); + } + + + /** + * Fill an already-allocated array with the values (more efficient than + * creating a new array each time). If 'array' is null, or not the same + * size as the number of values, a new array will be allocated and returned. + */ + public double[] valueArray(double[] array) { + if (array == null || array.length != size()) { + array = new double[count]; + } + System.arraycopy(values, 0, array, 0, count); + return array; + } + + + /** + * Return a value for the specified key. + * + * @webref doubledict:method + * @brief Return a value for the specified key + */ + public double get(String key) { + int index = index(key); + if (index == -1) { + throw new IllegalArgumentException("No key named '" + key + "'"); + } + return values[index]; + } + + + public double get(String key, double alternate) { + int index = index(key); + if (index == -1) { + return alternate; + } + return values[index]; + } + + + /** + * @webref doubledict:method + * @brief Create a new key/value pair or change the value of one + */ + public void set(String key, double amount) { + int index = index(key); + if (index == -1) { + create(key, amount); + } else { + values[index] = amount; + } + } + + + public void setIndex(int index, String key, double value) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + keys[index] = key; + values[index] = value; + } + + + /** + * @webref doubledict:method + * @brief Check if a key is a part of the data structure + */ + public boolean hasKey(String key) { + return index(key) != -1; + } + + + /** + * @webref doubledict:method + * @brief Add to a value + */ + public void add(String key, double amount) { + int index = index(key); + if (index == -1) { + create(key, amount); + } else { + values[index] += amount; + } + } + + + /** + * @webref doubledict:method + * @brief Subtract from a value + */ + public void sub(String key, double amount) { + add(key, -amount); + } + + + /** + * @webref doubledict:method + * @brief Multiply a value + */ + public void mult(String key, double amount) { + int index = index(key); + if (index != -1) { + values[index] *= amount; + } + } + + + /** + * @webref doubledict:method + * @brief Divide a value + */ + public void div(String key, double amount) { + int index = index(key); + if (index != -1) { + values[index] /= amount; + } + } + + + private void checkMinMax(String functionName) { + if (count == 0) { + String msg = + String.format("Cannot use %s() on an empty %s.", + functionName, getClass().getSimpleName()); + throw new RuntimeException(msg); + } + } + + + /** + * @webref doublelist:method + * @brief Return the smallest value + */ + public int minIndex() { + //checkMinMax("minIndex"); + if (count == 0) return -1; + + // Will still return NaN if there are 1 or more entries, and they're all NaN + double m = Float.NaN; + int mi = -1; + for (int i = 0; i < count; i++) { + // find one good value to start + if (values[i] == values[i]) { + m = values[i]; + mi = i; + + // calculate the rest + for (int j = i+1; j < count; j++) { + double d = values[j]; + if ((d == d) && (d < m)) { + m = values[j]; + mi = j; + } + } + break; + } + } + return mi; + } + + + // return the key for the minimum value + public String minKey() { + checkMinMax("minKey"); + int index = minIndex(); + if (index == -1) { + return null; + } + return keys[index]; + } + + + // return the minimum value, or throw an error if there are no values + public double minValue() { + checkMinMax("minValue"); + int index = minIndex(); + if (index == -1) { + return Float.NaN; + } + return values[index]; + } + + + /** + * @webref doublelist:method + * @brief Return the largest value + */ + // The index of the entry that has the max value. Reference above is incorrect. + public int maxIndex() { + //checkMinMax("maxIndex"); + if (count == 0) { + return -1; + } + // Will still return NaN if there is 1 or more entries, and they're all NaN + double m = Double.NaN; + int mi = -1; + for (int i = 0; i < count; i++) { + // find one good value to start + if (values[i] == values[i]) { + m = values[i]; + mi = i; + + // calculate the rest + for (int j = i+1; j < count; j++) { + double d = values[j]; + if (!Double.isNaN(d) && (d > m)) { + m = values[j]; + mi = j; + } + } + break; + } + } + return mi; + } + + + /** The key for a max value; null if empty or everything is NaN (no max). */ + public String maxKey() { + //checkMinMax("maxKey"); + int index = maxIndex(); + if (index == -1) { + return null; + } + return keys[index]; + } + + + /** The max value. (Or NaN if no entries or they're all NaN.) */ + public double maxValue() { + //checkMinMax("maxValue"); + int index = maxIndex(); + if (index == -1) { + return Float.NaN; + } + return values[index]; + } + + + public double sum() { + double sum = 0; + for (int i = 0; i < count; i++) { + sum += values[i]; + } + return sum; + } + + + public int index(String what) { + Integer found = indices.get(what); + return (found == null) ? -1 : found.intValue(); + } + + + protected void create(String what, double much) { + if (count == keys.length) { + keys = PApplet.expand(keys); + values = PApplet.expand(values); + } + indices.put(what, Integer.valueOf(count)); + keys[count] = what; + values[count] = much; + count++; + } + + + /** + * @webref doubledict:method + * @brief Remove a key/value pair + */ + public double remove(String key) { + int index = index(key); + if (index == -1) { + throw new NoSuchElementException("'" + key + "' not found"); + } + double value = values[index]; + removeIndex(index); + return value; + } + + + public double removeIndex(int index) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + double value = values[index]; + indices.remove(keys[index]); + for (int i = index; i < count-1; i++) { + keys[i] = keys[i+1]; + values[i] = values[i+1]; + indices.put(keys[i], i); + } + count--; + keys[count] = null; + values[count] = 0; + return value; + } + + + public void swap(int a, int b) { + String tkey = keys[a]; + double tvalue = values[a]; + keys[a] = keys[b]; + values[a] = values[b]; + keys[b] = tkey; + values[b] = tvalue; + +// indices.put(keys[a], Integer.valueOf(a)); +// indices.put(keys[b], Integer.valueOf(b)); + } + + + /** + * Sort the keys alphabetically (ignoring case). Uses the value as a + * tie-breaker (only really possible with a key that has a case change). + * + * @webref doubledict:method + * @brief Sort the keys alphabetically + */ + public void sortKeys() { + sortImpl(true, false, true); + } + + + /** + * @webref doubledict:method + * @brief Sort the keys alphabetically in reverse + */ + public void sortKeysReverse() { + sortImpl(true, true, true); + } + + + /** + * Sort by values in descending order (largest value will be at [0]). + * + * @webref doubledict:method + * @brief Sort by values in ascending order + */ + public void sortValues() { + sortValues(true); + } + + + /** + * Set true to ensure that the order returned is identical. Slightly + * slower because the tie-breaker for identical values compares the keys. + * @param stable + */ + public void sortValues(boolean stable) { + sortImpl(false, false, stable); + } + + + /** + * @webref doubledict:method + * @brief Sort by values in descending order + */ + public void sortValuesReverse() { + sortValuesReverse(true); + } + + + public void sortValuesReverse(boolean stable) { + sortImpl(false, true, stable); + } + + + protected void sortImpl(final boolean useKeys, final boolean reverse, + final boolean stable) { + Sort s = new Sort() { + @Override + public int size() { + if (useKeys) { + return count; // don't worry about NaN values + + } else if (count == 0) { // skip the NaN check, it'll AIOOBE + return 0; + + } else { // first move NaN values to the end of the list + int right = count - 1; + while (values[right] != values[right]) { + right--; + if (right == -1) { + return 0; // all values are NaN + } + } + for (int i = right; i >= 0; --i) { + if (Double.isNaN(values[i])) { + swap(i, right); + --right; + } + } + return right + 1; + } + } + + @Override + public int compare(int a, int b) { + double diff = 0; + if (useKeys) { + diff = keys[a].compareToIgnoreCase(keys[b]); + if (diff == 0) { + diff = values[a] - values[b]; + } + } else { // sort values + diff = values[a] - values[b]; + if (diff == 0 && stable) { + diff = keys[a].compareToIgnoreCase(keys[b]); + } + } + if (diff == 0) { + return 0; + } else if (reverse) { + return diff < 0 ? 1 : -1; + } else { + return diff < 0 ? -1 : 1; + } + } + + @Override + public void swap(int a, int b) { + DoubleDict.this.swap(a, b); + } + }; + s.run(); + + // Set the indices after sort/swaps (performance fix 160411) + resetIndices(); + } + + + /** + * Sum all of the values in this dictionary, then return a new FloatDict of + * each key, divided by the total sum. The total for all values will be ~1.0. + * @return a FloatDict with the original keys, mapped to their pct of the total + */ + public DoubleDict getPercent() { + double sum = sum(); + DoubleDict outgoing = new DoubleDict(); + for (int i = 0; i < size(); i++) { + double percent = value(i) / sum; + outgoing.set(key(i), percent); + } + return outgoing; + } + + + /** Returns a duplicate copy of this object. */ + public DoubleDict copy() { + DoubleDict outgoing = new DoubleDict(count); + System.arraycopy(keys, 0, outgoing.keys, 0, count); + System.arraycopy(values, 0, outgoing.values, 0, count); + for (int i = 0; i < count; i++) { + outgoing.indices.put(keys[i], i); + } + outgoing.count = count; + return outgoing; + } + + + public void print() { + for (int i = 0; i < size(); i++) { + System.out.println(keys[i] + " = " + values[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write tab-delimited entries out to + * @param writer + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(keys[i] + "\t" + values[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + StringList items = new StringList(); + for (int i = 0; i < count; i++) { + items.append(JSONObject.quote(keys[i])+ ": " + values[i]); + } + return "{ " + items.join(", ") + " }"; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); + } +} diff --git a/libs/processing-core/src/main/java/processing/data/DoubleList.java b/libs/processing-core/src/main/java/processing/data/DoubleList.java new file mode 100644 index 000000000..ae47a8442 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/data/DoubleList.java @@ -0,0 +1,928 @@ +package processing.data; + +import java.io.File; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Random; + +import processing.core.PApplet; + + +/** + * Helper class for a list of floats. Lists are designed to have some of the + * features of ArrayLists, but to maintain the simplicity and efficiency of + * working with arrays. + * + * Functions like sort() and shuffle() always act on the list itself. To get + * a sorted copy, use list.copy().sort(). + * + * @webref data:composite + * @see IntList + * @see StringList + */ +public class DoubleList implements Iterable { + int count; + double[] data; + + + public DoubleList() { + data = new double[10]; + } + + + /** + * @nowebref + */ + public DoubleList(int length) { + data = new double[length]; + } + + + /** + * @nowebref + */ + public DoubleList(double[] list) { + count = list.length; + data = new double[count]; + System.arraycopy(list, 0, data, 0, count); + } + + + /** + * Construct an FloatList from an iterable pile of objects. + * For instance, a double array, an array of strings, who knows). + * Un-parseable or null values will be set to NaN. + * @nowebref + */ + public DoubleList(Iterable iter) { + this(10); + for (Object o : iter) { + if (o == null) { + append(Double.NaN); + } else if (o instanceof Number) { + append(((Number) o).doubleValue()); + } else { + append(PApplet.parseFloat(o.toString().trim())); + } + } + crop(); + } + + + /** + * Construct an FloatList from a random pile of objects. + * Un-parseable or null values will be set to NaN. + */ + public DoubleList(Object... items) { + // nuts, no good way to pass missingValue to this fn (varargs must be last) + final double missingValue = Double.NaN; + + count = items.length; + data = new double[count]; + int index = 0; + for (Object o : items) { + double value = missingValue; + if (o != null) { + if (o instanceof Number) { + value = ((Number) o).doubleValue(); + } else { + try { + value = Double.parseDouble(o.toString().trim()); + } catch (NumberFormatException nfe) { + value = missingValue; + } + } + } + data[index++] = value; + } + } + + + /** + * Improve efficiency by removing allocated but unused entries from the + * internal array used to store the data. Set to private, though it could + * be useful to have this public if lists are frequently making drastic + * size changes (from very large to very small). + */ + private void crop() { + if (count != data.length) { + data = PApplet.subset(data, 0, count); + } + } + + + /** + * Get the length of the list. + * + * @webref doublelist:method + * @brief Get the length of the list + */ + public int size() { + return count; + } + + + public void resize(int length) { + if (length > data.length) { + double[] temp = new double[length]; + System.arraycopy(data, 0, temp, 0, count); + data = temp; + + } else if (length > count) { + Arrays.fill(data, count, length, 0); + } + count = length; + } + + + /** + * Remove all entries from the list. + * + * @webref doublelist:method + * @brief Remove all entries from the list + */ + public void clear() { + count = 0; + } + + + /** + * Get an entry at a particular index. + * + * @webref doublelist:method + * @brief Get an entry at a particular index + */ + public double get(int index) { + if (index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + return data[index]; + } + + + /** + * Set the entry at a particular index. If the index is past the length of + * the list, it'll expand the list to accommodate, and fill the intermediate + * entries with 0s. + * + * @webref doublelist:method + * @brief Set the entry at a particular index + */ + public void set(int index, double what) { + if (index >= count) { + data = PApplet.expand(data, index+1); + for (int i = count; i < index; i++) { + data[i] = 0; + } + count = index+1; + } + data[index] = what; + } + + + /** Just an alias for append(), but matches pop() */ + public void push(double value) { + append(value); + } + + + public double pop() { + if (count == 0) { + throw new RuntimeException("Can't call pop() on an empty list"); + } + double value = get(count-1); + count--; + return value; + } + + + /** + * Remove an element from the specified index. + * + * @webref doublelist:method + * @brief Remove an element from the specified index + */ + public double remove(int index) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + double entry = data[index]; +// int[] outgoing = new int[count - 1]; +// System.arraycopy(data, 0, outgoing, 0, index); +// count--; +// System.arraycopy(data, index + 1, outgoing, 0, count - index); +// data = outgoing; + // For most cases, this actually appears to be faster + // than arraycopy() on an array copying into itself. + for (int i = index; i < count-1; i++) { + data[i] = data[i+1]; + } + count--; + return entry; + } + + + // Remove the first instance of a particular value, + // and return the index at which it was found. + public int removeValue(int value) { + int index = index(value); + if (index != -1) { + remove(index); + return index; + } + return -1; + } + + + // Remove all instances of a particular value, + // and return the number of values found and removed + public int removeValues(int value) { + int ii = 0; + if (Double.isNaN(value)) { + for (int i = 0; i < count; i++) { + if (!Double.isNaN(data[i])) { + data[ii++] = data[i]; + } + } + } else { + for (int i = 0; i < count; i++) { + if (data[i] != value) { + data[ii++] = data[i]; + } + } + } + int removed = count - ii; + count = ii; + return removed; + } + + + /** Replace the first instance of a particular value */ + public boolean replaceValue(double value, double newValue) { + if (Double.isNaN(value)) { + for (int i = 0; i < count; i++) { + if (Double.isNaN(data[i])) { + data[i] = newValue; + return true; + } + } + } else { + int index = index(value); + if (index != -1) { + data[index] = newValue; + return true; + } + } + return false; + } + + + /** Replace all instances of a particular value */ + public boolean replaceValues(double value, double newValue) { + boolean changed = false; + if (Double.isNaN(value)) { + for (int i = 0; i < count; i++) { + if (Double.isNaN(data[i])) { + data[i] = newValue; + changed = true; + } + } + } else { + for (int i = 0; i < count; i++) { + if (data[i] == value) { + data[i] = newValue; + changed = true; + } + } + } + return changed; + } + + + + /** + * Add a new entry to the list. + * + * @webref doublelist:method + * @brief Add a new entry to the list + */ + public void append(double value) { + if (count == data.length) { + data = PApplet.expand(data); + } + data[count++] = value; + } + + + public void append(double[] values) { + for (double v : values) { + append(v); + } + } + + + public void append(DoubleList list) { + for (double v : list.values()) { // will concat the list... + append(v); + } + } + + + /** Add this value, but only if it's not already in the list. */ + public void appendUnique(double value) { + if (!hasValue(value)) { + append(value); + } + } + + +// public void insert(int index, int value) { +// if (index+1 > count) { +// if (index+1 < data.length) { +// } +// } +// if (index >= data.length) { +// data = PApplet.expand(data, index+1); +// data[index] = value; +// count = index+1; +// +// } else if (count == data.length) { +// if (index >= count) { +// //int[] temp = new int[count << 1]; +// System.arraycopy(data, 0, temp, 0, index); +// temp[index] = value; +// System.arraycopy(data, index, temp, index+1, count - index); +// data = temp; +// +// } else { +// // data[] has room to grow +// // for() loop believed to be faster than System.arraycopy over itself +// for (int i = count; i > index; --i) { +// data[i] = data[i-1]; +// } +// data[index] = value; +// count++; +// } +// } + + + public void insert(int index, double value) { + insert(index, new double[] { value }); + } + + + // same as splice + public void insert(int index, double[] values) { + if (index < 0) { + throw new IllegalArgumentException("insert() index cannot be negative: it was " + index); + } + if (index >= data.length) { + throw new IllegalArgumentException("insert() index " + index + " is past the end of this list"); + } + + double[] temp = new double[count + values.length]; + + // Copy the old values, but not more than already exist + System.arraycopy(data, 0, temp, 0, Math.min(count, index)); + + // Copy the new values into the proper place + System.arraycopy(values, 0, temp, index, values.length); + +// if (index < count) { + // The index was inside count, so it's a true splice/insert + System.arraycopy(data, index, temp, index+values.length, count - index); + count = count + values.length; +// } else { +// // The index was past 'count', so the new count is weirder +// count = index + values.length; +// } + data = temp; + } + + + public void insert(int index, DoubleList list) { + insert(index, list.values()); + } + + + // below are aborted attempts at more optimized versions of the code + // that are harder to read and debug... + +// if (index + values.length >= count) { +// // We're past the current 'count', check to see if we're still allocated +// // index 9, data.length = 10, values.length = 1 +// if (index + values.length < data.length) { +// // There's still room for these entries, even though it's past 'count'. +// // First clear out the entries leading up to it, however. +// for (int i = count; i < index; i++) { +// data[i] = 0; +// } +// data[index] = +// } +// if (index >= data.length) { +// int length = index + values.length; +// int[] temp = new int[length]; +// System.arraycopy(data, 0, temp, 0, count); +// System.arraycopy(values, 0, temp, index, values.length); +// data = temp; +// count = data.length; +// } else { +// +// } +// +// } else if (count == data.length) { +// int[] temp = new int[count << 1]; +// System.arraycopy(data, 0, temp, 0, index); +// temp[index] = value; +// System.arraycopy(data, index, temp, index+1, count - index); +// data = temp; +// +// } else { +// // data[] has room to grow +// // for() loop believed to be faster than System.arraycopy over itself +// for (int i = count; i > index; --i) { +// data[i] = data[i-1]; +// } +// data[index] = value; +// count++; +// } + + + /** Return the first index of a particular value. */ + public int index(double what) { + /* + if (indexCache != null) { + try { + return indexCache.get(what); + } catch (Exception e) { // not there + return -1; + } + } + */ + for (int i = 0; i < count; i++) { + if (data[i] == what) { + return i; + } + } + return -1; + } + + + /** + * @webref doublelist:method + * @brief Check if a number is a part of the list + */ + public boolean hasValue(double value) { + if (Double.isNaN(value)) { + for (int i = 0; i < count; i++) { + if (Double.isNaN(data[i])) { + return true; + } + } + } else { + for (int i = 0; i < count; i++) { + if (data[i] == value) { + return true; + } + } + } + return false; + } + + + private void boundsProblem(int index, String method) { + final String msg = String.format("The list size is %d. " + + "You cannot %s() to element %d.", count, method, index); + throw new ArrayIndexOutOfBoundsException(msg); + } + + + /** + * @webref doublelist:method + * @brief Add to a value + */ + public void add(int index, double amount) { + if (index < count) { + data[index] += amount; + } else { + boundsProblem(index, "add"); + } + } + + + /** + * @webref doublelist:method + * @brief Subtract from a value + */ + public void sub(int index, double amount) { + if (index < count) { + data[index] -= amount; + } else { + boundsProblem(index, "sub"); + } + } + + + /** + * @webref doublelist:method + * @brief Multiply a value + */ + public void mult(int index, double amount) { + if (index < count) { + data[index] *= amount; + } else { + boundsProblem(index, "mult"); + } + } + + + /** + * @webref doublelist:method + * @brief Divide a value + */ + public void div(int index, double amount) { + if (index < count) { + data[index] /= amount; + } else { + boundsProblem(index, "div"); + } + } + + + private void checkMinMax(String functionName) { + if (count == 0) { + String msg = + String.format("Cannot use %s() on an empty %s.", + functionName, getClass().getSimpleName()); + throw new RuntimeException(msg); + } + } + + + /** + * @webref doublelist:method + * @brief Return the smallest value + */ + public double min() { + checkMinMax("min"); + int index = minIndex(); + return index == -1 ? Double.NaN : data[index]; + } + + + public int minIndex() { + checkMinMax("minIndex"); + double m = Double.NaN; + int mi = -1; + for (int i = 0; i < count; i++) { + // find one good value to start + if (data[i] == data[i]) { + m = data[i]; + mi = i; + + // calculate the rest + for (int j = i+1; j < count; j++) { + double d = data[j]; + if (!Double.isNaN(d) && (d < m)) { + m = data[j]; + mi = j; + } + } + break; + } + } + return mi; + } + + + /** + * @webref doublelist:method + * @brief Return the largest value + */ + public double max() { + checkMinMax("max"); + int index = maxIndex(); + return index == -1 ? Double.NaN : data[index]; + } + + + public int maxIndex() { + checkMinMax("maxIndex"); + double m = Double.NaN; + int mi = -1; + for (int i = 0; i < count; i++) { + // find one good value to start + if (data[i] == data[i]) { + m = data[i]; + mi = i; + + // calculate the rest + for (int j = i+1; j < count; j++) { + double d = data[j]; + if (!Double.isNaN(d) && (d > m)) { + m = data[j]; + mi = j; + } + } + break; + } + } + return mi; + } + + + public double sum() { + double sum = 0; + for (int i = 0; i < count; i++) { + sum += data[i]; + } + return sum; + } + + + /** + * Sorts the array in place. + * + * @webref doublelist:method + * @brief Sorts an array, lowest to highest + */ + public void sort() { + Arrays.sort(data, 0, count); + } + + + /** + * Reverse sort, orders values from highest to lowest + * + * @webref doublelist:method + * @brief Reverse sort, orders values from highest to lowest + */ + public void sortReverse() { + new Sort() { + @Override + public int size() { + // if empty, don't even mess with the NaN check, it'll AIOOBE + if (count == 0) { + return 0; + } + // move NaN values to the end of the list and don't sort them + int right = count - 1; + while (data[right] != data[right]) { + right--; + if (right == -1) { // all values are NaN + return 0; + } + } + for (int i = right; i >= 0; --i) { + double v = data[i]; + if (v != v) { + data[i] = data[right]; + data[right] = v; + --right; + } + } + return right + 1; + } + + @Override + public int compare(int a, int b) { + double diff = data[b] - data[a]; + return diff == 0 ? 0 : (diff < 0 ? -1 : 1); + } + + @Override + public void swap(int a, int b) { + double temp = data[a]; + data[a] = data[b]; + data[b] = temp; + } + }.run(); + } + + + // use insert() +// public void splice(int index, int value) { +// } + + +// public void subset(int start) { +// subset(start, count - start); +// } + + +// public void subset(int start, int num) { +// for (int i = 0; i < num; i++) { +// data[i] = data[i+start]; +// } +// count = num; +// } + + + /** + * @webref doublelist:method + * @brief Reverse the order of the list elements + */ + public void reverse() { + int ii = count - 1; + for (int i = 0; i < count/2; i++) { + double t = data[i]; + data[i] = data[ii]; + data[ii] = t; + --ii; + } + } + + + /** + * Randomize the order of the list elements. Note that this does not + * obey the randomSeed() function in PApplet. + * + * @webref doublelist:method + * @brief Randomize the order of the list elements + */ + public void shuffle() { + Random r = new Random(); + int num = count; + while (num > 1) { + int value = r.nextInt(num); + num--; + double temp = data[num]; + data[num] = data[value]; + data[value] = temp; + } + } + + + /** + * Randomize the list order using the random() function from the specified + * sketch, allowing shuffle() to use its current randomSeed() setting. + */ + public void shuffle(PApplet sketch) { + int num = count; + while (num > 1) { + int value = (int) sketch.random(num); + num--; + double temp = data[num]; + data[num] = data[value]; + data[value] = temp; + } + } + + + public DoubleList copy() { + DoubleList outgoing = new DoubleList(data); + outgoing.count = count; + return outgoing; + } + + + /** + * Returns the actual array being used to store the data. For advanced users, + * this is the fastest way to access a large list. Suitable for iterating + * with a for() loop, but modifying the list will have terrible consequences. + */ + public double[] values() { + crop(); + return data; + } + + + /** Implemented this way so that we can use a FloatList in a for loop. */ + @Override + public Iterator iterator() { +// } +// +// +// public Iterator valueIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + DoubleList.this.remove(index); + index--; + } + + public Double next() { + return data[++index]; + } + + public boolean hasNext() { + return index+1 < count; + } + }; + } + + + /** + * Create a new array with a copy of all the values. + * @return an array sized by the length of the list with each of the values. + * @webref doublelist:method + * @brief Create a new array with a copy of all the values + */ + public double[] array() { + return array(null); + } + + + /** + * Copy values into the specified array. If the specified array is null or + * not the same size, a new array will be allocated. + * @param array + */ + public double[] array(double[] array) { + if (array == null || array.length != count) { + array = new double[count]; + } + System.arraycopy(data, 0, array, 0, count); + return array; + } + + + /** + * Returns a normalized version of this array. Called getPercent() for + * consistency with the Dict classes. It's a getter method because it needs + * to returns a new list (because IntList/Dict can't do percentages or + * normalization in place on int values). + */ + public DoubleList getPercent() { + double sum = 0; + for (double value : array()) { + sum += value; + } + DoubleList outgoing = new DoubleList(count); + for (int i = 0; i < count; i++) { + double percent = data[i] / sum; + outgoing.set(i, percent); + } + return outgoing; + } + + + public DoubleList getSubset(int start) { + return getSubset(start, count - start); + } + + + public DoubleList getSubset(int start, int num) { + double[] subset = new double[num]; + System.arraycopy(data, start, subset, 0, num); + return new DoubleList(subset); + } + + + public String join(String separator) { + if (count == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append(data[0]); + for (int i = 1; i < count; i++) { + sb.append(separator); + sb.append(data[i]); + } + return sb.toString(); + } + + + public void print() { + for (int i = 0; i < count; i++) { + System.out.format("[%d] %f%n", i, data[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write entries to a PrintWriter, one per line + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(data[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + return "[ " + join(", ") + " ]"; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); + } +} diff --git a/core/src/processing/data/FloatDict.java b/libs/processing-core/src/main/java/processing/data/FloatDict.java similarity index 56% rename from core/src/processing/data/FloatDict.java rename to libs/processing-core/src/main/java/processing/data/FloatDict.java index 8d146ee74..9495563ad 100644 --- a/core/src/processing/data/FloatDict.java +++ b/libs/processing-core/src/main/java/processing/data/FloatDict.java @@ -3,6 +3,7 @@ import java.io.*; import java.util.HashMap; import java.util.Iterator; +import java.util.NoSuchElementException; import processing.core.PApplet; @@ -23,7 +24,7 @@ public class FloatDict { protected float[] values; /** Internal implementation for faster lookups */ - private HashMap indices = new HashMap(); + private HashMap indices = new HashMap<>(); public FloatDict() { @@ -53,23 +54,22 @@ public FloatDict(int length) { * @nowebref */ public FloatDict(BufferedReader reader) { -// public FloatHash(PApplet parent, String filename) { String[] lines = PApplet.loadStrings(reader); keys = new String[lines.length]; values = new float[lines.length]; -// boolean csv = (lines[0].indexOf('\t') == -1); for (int i = 0; i < lines.length; i++) { -// String[] pieces = csv ? Table.splitLineCSV(lines[i]) : PApplet.split(lines[i], '\t'); String[] pieces = PApplet.split(lines[i], '\t'); if (pieces.length == 2) { keys[count] = pieces[0]; values[count] = PApplet.parseFloat(pieces[1]); + indices.put(pieces[0], count); count++; } } } + /** * @nowebref */ @@ -85,6 +85,28 @@ public FloatDict(String[] keys, float[] values) { } } + + /** + * Constructor to allow (more intuitive) inline initialization, e.g.: + *
    +   * new FloatDict(new Object[][] {
    +   *   { "key1", 1 },
    +   *   { "key2", 2 }
    +   * });
    +   * 
    + */ + public FloatDict(Object[][] pairs) { + count = pairs.length; + this.keys = new String[count]; + this.values = new float[count]; + for (int i = 0; i < count; i++) { + keys[i] = (String) pairs[i][0]; + values[i] = (Float) pairs[i][1]; + indices.put(keys[i], i); + } + } + + /** * @webref floatdict:method * @brief Returns the number of key/value pairs @@ -94,6 +116,31 @@ public int size() { } + /** + * Resize the internal data, this can only be used to shrink the list. + * Helpful for situations like sorting and then grabbing the top 50 entries. + */ + public void resize(int length) { + if (length == count) return; + + if (length > count) { + throw new IllegalArgumentException("resize() can only be used to shrink the dictionary"); + } + if (length < 1) { + throw new IllegalArgumentException("resize(" + length + ") is too small, use 1 or higher"); + } + + String[] newKeys = new String[length]; + float[] newValues = new float[length]; + PApplet.arrayCopy(keys, newKeys, length); + PApplet.arrayCopy(values, newValues, length); + keys = newKeys; + values = newValues; + count = length; + resetIndices(); + } + + /** * Remove all entries. * @@ -102,10 +149,67 @@ public int size() { */ public void clear() { count = 0; - indices = new HashMap(); + indices = new HashMap<>(); + } + + + private void resetIndices() { + indices = new HashMap<>(count); + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public class Entry { + public String key; + public float value; + + Entry(String key, float value) { + this.key = key; + this.value = value; + } + } + + + public Iterable entries() { + return new Iterable() { + + public Iterator iterator() { + return entryIterator(); + } + }; + } + + + public Iterator entryIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Entry next() { + ++index; + Entry e = new Entry(keys[index], values[index]); + return e; + } + + public boolean hasNext() { + return index+1 < size(); + } + }; } + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + public String key(int index) { return keys[index]; } @@ -119,70 +223,36 @@ protected void crop() { } -// /** -// * Return the internal array being used to store the keys. Allocated but -// * unused entries will be removed. This array should not be modified. -// */ -// public String[] keys() { -// crop(); -// return keys; -// } - - /** - * @webref floatdict:method - * @brief Return the internal array being used to store the keys - */ public Iterable keys() { return new Iterable() { + @Override public Iterator iterator() { - return new Iterator() { - int index = -1; - - public void remove() { - removeIndex(index); - } - - public String next() { - return key(++index); - } - - public boolean hasNext() { - return index+1 < size(); - } - }; + return keyIterator(); } }; } - /* - static class KeyIterator implements Iterator { - FloatHash parent; - int index; - - public KeyIterator(FloatHash parent) { - this.parent = parent; - index = -1; - } - - public void remove() { - parent.removeIndex(index); - } + // Use this to iterate when you want to be able to remove elements along the way + public Iterator keyIterator() { + return new Iterator() { + int index = -1; - public String next() { - return parent.key(++index); - } + public void remove() { + removeIndex(index); + index--; + } - public boolean hasNext() { - return index+1 < parent.size(); - } + public String next() { + return key(++index); + } - public void reset() { - index = -1; - } + public boolean hasNext() { + return index+1 < size(); + } + }; } - */ /** @@ -192,6 +262,7 @@ public void reset() { * @brief Return a copy of the internal keys array */ public String[] keyArray() { + crop(); return keyArray(null); } @@ -210,11 +281,6 @@ public float value(int index) { } -// public float[] values() { -// crop(); -// return values; -// } - /** * @webref floatdict:method * @brief Return the internal array being used to store the values @@ -222,22 +288,29 @@ public float value(int index) { public Iterable values() { return new Iterable() { + @Override public Iterator iterator() { - return new Iterator() { - int index = -1; + return valueIterator(); + } + }; + } - public void remove() { - removeIndex(index); - } - public Float next() { - return value(++index); - } + public Iterator valueIterator() { + return new Iterator() { + int index = -1; - public boolean hasNext() { - return index+1 < size(); - } - }; + public void remove() { + removeIndex(index); + index--; + } + + public Float next() { + return value(++index); + } + + public boolean hasNext() { + return index+1 < size(); } }; } @@ -250,6 +323,7 @@ public boolean hasNext() { * @brief Create a new array and copy each of the values into it */ public float[] valueArray() { + crop(); return valueArray(null); } @@ -276,7 +350,18 @@ public float[] valueArray(float[] array) { */ public float get(String key) { int index = index(key); - if (index == -1) return 0; + if (index == -1) { + throw new IllegalArgumentException("No key named '" + key + "'"); + } + return values[index]; + } + + + public float get(String key, float alternate) { + int index = index(key); + if (index == -1) { + return alternate; + } return values[index]; } @@ -295,6 +380,15 @@ public void set(String key, float amount) { } + public void setIndex(int index, String key, float value) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + keys[index] = key; + values[index] = value; + } + + /** * @webref floatdict:method * @brief Check if a key is a part of the data structure @@ -304,18 +398,6 @@ public boolean hasKey(String key) { } -// /** Increase the value of a specific key by 1. */ -// public void inc(String key) { -// inc(key, 1); -//// int index = index(key); -//// if (index == -1) { -//// create(key, 1); -//// } else { -//// values[index]++; -//// } -// } - - /** * @webref floatdict:method * @brief Add to a value @@ -330,12 +412,6 @@ public void add(String key, float amount) { } -// /** Decrease the value of a key by 1. */ -// public void dec(String key) { -// inc(key, -1); -// } - - /** * @webref floatdict:method * @brief Subtract from a value @@ -384,8 +460,10 @@ private void checkMinMax(String functionName) { * @brief Return the smallest value */ public int minIndex() { - checkMinMax("minIndex"); - // Will still return NaN if there is 1 or more entries, and they're all NaN + //checkMinMax("minIndex"); + if (count == 0) return -1; + + // Will still return NaN if there are 1 or more entries, and they're all NaN float m = Float.NaN; int mi = -1; for (int i = 0; i < count; i++) { @@ -397,7 +475,7 @@ public int minIndex() { // calculate the rest for (int j = i+1; j < count; j++) { float d = values[j]; - if (!Float.isNaN(d) && (d < m)) { + if ((d == d) && (d < m)) { m = values[j]; mi = j; } @@ -409,15 +487,25 @@ public int minIndex() { } + // return the key for the minimum value public String minKey() { checkMinMax("minKey"); - return keys[minIndex()]; + int index = minIndex(); + if (index == -1) { + return null; + } + return keys[index]; } + // return the minimum value, or throw an error if there are no values public float minValue() { checkMinMax("minValue"); - return values[minIndex()]; + int index = minIndex(); + if (index == -1) { + return Float.NaN; + } + return values[index]; } @@ -427,7 +515,10 @@ public float minValue() { */ // The index of the entry that has the max value. Reference above is incorrect. public int maxIndex() { - checkMinMax("maxIndex"); + //checkMinMax("maxIndex"); + if (count == 0) { + return -1; + } // Will still return NaN if there is 1 or more entries, and they're all NaN float m = Float.NaN; int mi = -1; @@ -452,17 +543,46 @@ public int maxIndex() { } - /** The key for a max value. */ + /** The key for a max value; null if empty or everything is NaN (no max). */ public String maxKey() { - checkMinMax("maxKey"); - return keys[maxIndex()]; + //checkMinMax("maxKey"); + int index = maxIndex(); + if (index == -1) { + return null; + } + return keys[index]; } - /** The max value. */ + /** The max value. (Or NaN if no entries or they're all NaN.) */ public float maxValue() { - checkMinMax("maxValue"); - return values[maxIndex()]; + //checkMinMax("maxValue"); + int index = maxIndex(); + if (index == -1) { + return Float.NaN; + } + return values[index]; + } + + + public float sum() { + double amount = sumDouble(); + if (amount > Float.MAX_VALUE) { + throw new RuntimeException("sum() exceeds " + Float.MAX_VALUE + ", use sumDouble()"); + } + if (amount < -Float.MAX_VALUE) { + throw new RuntimeException("sum() lower than " + -Float.MAX_VALUE + ", use sumDouble()"); + } + return (float) amount; + } + + + public double sumDouble() { + double sum = 0; + for (int i = 0; i < count; i++) { + sum += values[i]; + } + return sum; } @@ -477,7 +597,7 @@ protected void create(String what, float much) { keys = PApplet.expand(keys); values = PApplet.expand(values); } - indices.put(what, new Integer(count)); + indices.put(what, Integer.valueOf(count)); keys[count] = what; values[count] = much; count++; @@ -488,21 +608,22 @@ protected void create(String what, float much) { * @webref floatdict:method * @brief Remove a key/value pair */ - public int remove(String key) { + public float remove(String key) { int index = index(key); - if (index != -1) { - removeIndex(index); + if (index == -1) { + throw new NoSuchElementException("'" + key + "' not found"); } - return index; + float value = values[index]; + removeIndex(index); + return value; } - public String removeIndex(int index) { + public float removeIndex(int index) { if (index < 0 || index >= count) { throw new ArrayIndexOutOfBoundsException(index); } - String key = keys[index]; - //System.out.println("index is " + which + " and " + keys[which]); + float value = values[index]; indices.remove(keys[index]); for (int i = index; i < count-1; i++) { keys[i] = keys[i+1]; @@ -512,11 +633,11 @@ public String removeIndex(int index) { count--; keys[count] = null; values[count] = 0; - return key; + return value; } - protected void swap(int a, int b) { + public void swap(int a, int b) { String tkey = keys[a]; float tvalue = values[a]; keys[a] = keys[b]; @@ -524,24 +645,11 @@ protected void swap(int a, int b) { keys[b] = tkey; values[b] = tvalue; - indices.put(keys[a], new Integer(a)); - indices.put(keys[b], new Integer(b)); +// indices.put(keys[a], Integer.valueOf(a)); +// indices.put(keys[b], Integer.valueOf(b)); } -// abstract class InternalSort extends Sort { -// @Override -// public int size() { -// return count; -// } -// -// @Override -// public void swap(int a, int b) { -// FloatHash.this.swap(a, b); -// } -// } - - /** * Sort the keys alphabetically (ignoring case). Uses the value as a * tie-breaker (only really possible with a key that has a case change). @@ -550,36 +658,16 @@ protected void swap(int a, int b) { * @brief Sort the keys alphabetically */ public void sortKeys() { - sortImpl(true, false); -// new InternalSort() { -// @Override -// public float compare(int a, int b) { -// int result = keys[a].compareToIgnoreCase(keys[b]); -// if (result != 0) { -// return result; -// } -// return values[b] - values[a]; -// } -// }.run(); + sortImpl(true, false, true); } /** * @webref floatdict:method - * @brief Sort the keys alphabetially in reverse + * @brief Sort the keys alphabetically in reverse */ public void sortKeysReverse() { - sortImpl(true, true); -// new InternalSort() { -// @Override -// public float compare(int a, int b) { -// int result = keys[b].compareToIgnoreCase(keys[a]); -// if (result != 0) { -// return result; -// } -// return values[a] - values[b]; -// } -// }.run(); + sortImpl(true, true, true); } @@ -590,13 +678,17 @@ public void sortKeysReverse() { * @brief Sort by values in ascending order */ public void sortValues() { - sortImpl(false, false); -// new InternalSort() { -// @Override -// public float compare(int a, int b) { -// -// } -// }.run(); + sortValues(true); + } + + + /** + * Set true to ensure that the order returned is identical. Slightly + * slower because the tie-breaker for identical values compares the keys. + * @param stable + */ + public void sortValues(boolean stable) { + sortImpl(false, false, stable); } @@ -605,71 +697,65 @@ public void sortValues() { * @brief Sort by values in descending order */ public void sortValuesReverse() { - sortImpl(false, true); -// new InternalSort() { -// @Override -// public float compare(int a, int b) { -// float diff = values[b] - values[a]; -// if (diff == 0 && keys[a] != null && keys[b] != null) { -// diff = keys[a].compareToIgnoreCase(keys[b]); -// } -// return descending ? diff : -diff; -// } -// }.run(); - } - - -// // ascending puts the largest value at the end -// // descending puts the largest value at 0 -// public void sortValues(final boolean descending, final boolean tiebreaker) { -// Sort s = new Sort() { -// @Override -// public int size() { -// return count; -// } -// -// @Override -// public float compare(int a, int b) { -// float diff = values[b] - values[a]; -// if (tiebreaker) { -// if (diff == 0) { -// diff = keys[a].compareToIgnoreCase(keys[b]); -// } -// } -// return descending ? diff : -diff; -// } -// -// @Override -// public void swap(int a, int b) { -// FloatHash.this.swap(a, b); -// } -// }; -// s.run(); -// } - - - protected void sortImpl(final boolean useKeys, final boolean reverse) { + sortValuesReverse(true); + } + + + public void sortValuesReverse(boolean stable) { + sortImpl(false, true, stable); + } + + + protected void sortImpl(final boolean useKeys, final boolean reverse, + final boolean stable) { Sort s = new Sort() { @Override public int size() { - return count; + if (useKeys) { + return count; // don't worry about NaN values + + } else if (count == 0) { // skip the NaN check, it'll AIOOBE + return 0; + + } else { // first move NaN values to the end of the list + int right = count - 1; + while (values[right] != values[right]) { + right--; + if (right == -1) { + return 0; // all values are NaN + } + } + for (int i = right; i >= 0; --i) { + if (Float.isNaN(values[i])) { + swap(i, right); + --right; + } + } + return right + 1; + } } @Override - public float compare(int a, int b) { + public int compare(int a, int b) { float diff = 0; if (useKeys) { diff = keys[a].compareToIgnoreCase(keys[b]); if (diff == 0) { - return values[a] - values[b]; + diff = values[a] - values[b]; } } else { // sort values diff = values[a] - values[b]; - if (diff == 0) { + if (diff == 0 && stable) { diff = keys[a].compareToIgnoreCase(keys[b]); } } - return reverse ? -diff : diff; + if (diff == 0) { + return 0; + } else if (reverse) { + return diff < 0 ? 1 : -1; + } else { + return diff < 0 ? -1 : 1; + } } @Override @@ -678,19 +764,19 @@ public void swap(int a, int b) { } }; s.run(); + + // Set the indices after sort/swaps (performance fix 160411) + resetIndices(); } /** * Sum all of the values in this dictionary, then return a new FloatDict of * each key, divided by the total sum. The total for all values will be ~1.0. - * @return a Dict with the original keys, mapped to their pct of the total + * @return a FloatDict with the original keys, mapped to their pct of the total */ public FloatDict getPercent() { - double sum = 0; - for (float value : valueArray()) { - sum += value; - } + double sum = sum(); FloatDict outgoing = new FloatDict(); for (int i = 0; i < size(); i++) { double percent = value(i) / sum; @@ -713,12 +799,21 @@ public FloatDict copy() { } -// /** -// * Write tab-delimited entries out to the console. -// */ -// public void print() { -// write(new PrintWriter(System.out)); -// } + public void print() { + for (int i = 0; i < size(); i++) { + System.out.println(keys[i] + " = " + values[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } /** @@ -733,17 +828,20 @@ public void write(PrintWriter writer) { } + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + StringList items = new StringList(); + for (int i = 0; i < count; i++) { + items.append(JSONObject.quote(keys[i])+ ": " + values[i]); + } + return "{ " + items.join(", ") + " }"; + } + + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName() + " size=" + size() + " { "); - for (int i = 0; i < size(); i++) { - if (i != 0) { - sb.append(", "); - } - sb.append("\"" + keys[i] + "\": " + values[i]); - } - sb.append(" }"); - return sb.toString(); + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } } diff --git a/core/src/processing/data/FloatList.java b/libs/processing-core/src/main/java/processing/data/FloatList.java similarity index 77% rename from core/src/processing/data/FloatList.java rename to libs/processing-core/src/main/java/processing/data/FloatList.java index f17f44784..863b05658 100644 --- a/core/src/processing/data/FloatList.java +++ b/libs/processing-core/src/main/java/processing/data/FloatList.java @@ -1,5 +1,7 @@ package processing.data; +import java.io.File; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Iterator; import java.util.Random; @@ -28,6 +30,7 @@ public FloatList() { data = new float[10]; } + /** * @nowebref */ @@ -35,6 +38,7 @@ public FloatList(int length) { data = new float[length]; } + /** * @nowebref */ @@ -44,13 +48,49 @@ public FloatList(float[] list) { System.arraycopy(list, 0, data, 0, count); } + /** + * Construct an FloatList from an iterable pile of objects. + * For instance, a float array, an array of strings, who knows). + * Un-parseable or null values will be set to NaN. * @nowebref */ - public FloatList(Iterable iter) { + public FloatList(Iterable iter) { this(10); - for (float v : iter) { - append(v); + for (Object o : iter) { + if (o == null) { + append(Float.NaN); + } else if (o instanceof Number) { + append(((Number) o).floatValue()); + } else { + append(PApplet.parseFloat(o.toString().trim())); + } + } + crop(); + } + + + /** + * Construct an FloatList from a random pile of objects. + * Un-parseable or null values will be set to NaN. + */ + public FloatList(Object... items) { + // nuts, no good way to pass missingValue to this fn (varargs must be last) + final float missingValue = Float.NaN; + + count = items.length; + data = new float[count]; + int index = 0; + for (Object o : items) { + float value = missingValue; + if (o != null) { + if (o instanceof Number) { + value = ((Number) o).floatValue(); + } else { + value = PApplet.parseFloat(o.toString().trim(), missingValue); + } + } + data[index++] = value; } } @@ -110,6 +150,9 @@ public void clear() { * @brief Get an entry at a particular index */ public float get(int index) { + if (index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } return data[index]; } @@ -134,6 +177,22 @@ public void set(int index, float what) { } + /** Just an alias for append(), but matches pop() */ + public void push(float value) { + append(value); + } + + + public float pop() { + if (count == 0) { + throw new RuntimeException("Can't call pop() on an empty list"); + } + float value = get(count-1); + count--; + return value; + } + + /** * Remove an element from the specified index. * @@ -266,6 +325,14 @@ public void append(FloatList list) { } + /** Add this value, but only if it's not already in the list. */ + public void appendUnique(float value) { + if (!hasValue(value)) { + append(value); + } + } + + // public void insert(int index, int value) { // if (index+1 > count) { // if (index+1 < data.length) { @@ -296,8 +363,13 @@ public void append(FloatList list) { // } + public void insert(int index, float value) { + insert(index, new float[] { value }); + } + + // same as splice - public void insert(int index, int[] values) { + public void insert(int index, float[] values) { if (index < 0) { throw new IllegalArgumentException("insert() index cannot be negative: it was " + index); } @@ -325,7 +397,7 @@ public void insert(int index, int[] values) { } - public void insert(int index, IntList list) { + public void insert(int index, FloatList list) { insert(index, list.values()); } @@ -415,12 +487,23 @@ public boolean hasValue(float value) { } + private void boundsProblem(int index, String method) { + final String msg = String.format("The list size is %d. " + + "You cannot %s() to element %d.", count, method, index); + throw new ArrayIndexOutOfBoundsException(msg); + } + + /** * @webref floatlist:method * @brief Add to a value */ public void add(int index, float amount) { - data[index] += amount; + if (index < count) { + data[index] += amount; + } else { + boundsProblem(index, "add"); + } } @@ -429,7 +512,11 @@ public void add(int index, float amount) { * @brief Subtract from a value */ public void sub(int index, float amount) { - data[index] -= amount; + if (index < count) { + data[index] -= amount; + } else { + boundsProblem(index, "sub"); + } } @@ -438,7 +525,11 @@ public void sub(int index, float amount) { * @brief Multiply a value */ public void mult(int index, float amount) { - data[index] *= amount; + if (index < count) { + data[index] *= amount; + } else { + boundsProblem(index, "mult"); + } } @@ -447,7 +538,11 @@ public void mult(int index, float amount) { * @brief Divide a value */ public void div(int index, float amount) { - data[index] /= amount; + if (index < count) { + data[index] /= amount; + } else { + boundsProblem(index, "div"); + } } @@ -534,11 +629,23 @@ public int maxIndex() { public float sum() { - double outgoing = 0; + double amount = sumDouble(); + if (amount > Float.MAX_VALUE) { + throw new RuntimeException("sum() exceeds " + Float.MAX_VALUE + ", use sumDouble()"); + } + if (amount < -Float.MAX_VALUE) { + throw new RuntimeException("sum() lower than " + -Float.MAX_VALUE + ", use sumDouble()"); + } + return (float) amount; + } + + + public double sumDouble() { + double sum = 0; for (int i = 0; i < count; i++) { - outgoing += data[i]; + sum += data[i]; } - return (float) outgoing; + return sum; } @@ -563,12 +670,33 @@ public void sortReverse() { new Sort() { @Override public int size() { - return count; + // if empty, don't even mess with the NaN check, it'll AIOOBE + if (count == 0) { + return 0; + } + // move NaN values to the end of the list and don't sort them + int right = count - 1; + while (data[right] != data[right]) { + right--; + if (right == -1) { // all values are NaN + return 0; + } + } + for (int i = right; i >= 0; --i) { + float v = data[i]; + if (v != v) { + data[i] = data[right]; + data[right] = v; + --right; + } + } + return right + 1; } @Override - public float compare(int a, int b) { - return data[b] - data[a]; + public int compare(int a, int b) { + float diff = data[b] - data[a]; + return diff == 0 ? 0 : (diff < 0 ? -1 : 1); } @Override @@ -601,7 +729,7 @@ public void swap(int a, int b) { /** * @webref floatlist:method - * @brief Reverse sort, orders values by first digit + * @brief Reverse the order of the list elements */ public void reverse() { int ii = count - 1; @@ -669,6 +797,7 @@ public float[] values() { /** Implemented this way so that we can use a FloatList in a for loop. */ + @Override public Iterator iterator() { // } // @@ -679,6 +808,7 @@ public Iterator iterator() { public void remove() { FloatList.this.remove(index); + index--; } public Float next() { @@ -704,7 +834,8 @@ public float[] array() { /** - * Copy as many values as possible into the specified array. + * Copy values into the specified array. If the specified array is null or + * not the same size, a new array will be allocated. * @param array */ public float[] array(float[] array) { @@ -762,17 +893,44 @@ public String join(String separator) { } + public void print() { + for (int i = 0; i < count; i++) { + System.out.format("[%d] %f%n", i, data[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write entries to a PrintWriter, one per line + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(data[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + return "[ " + join(", ") + " ]"; + } + + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName() + " size=" + size() + " [ "); - for (int i = 0; i < size(); i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(i + ": " + data[i]); - } - sb.append(" ]"); - return sb.toString(); + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } } diff --git a/core/src/processing/data/IntDict.java b/libs/processing-core/src/main/java/processing/data/IntDict.java similarity index 62% rename from core/src/processing/data/IntDict.java rename to libs/processing-core/src/main/java/processing/data/IntDict.java index 7bc2bae55..96913591f 100644 --- a/core/src/processing/data/IntDict.java +++ b/libs/processing-core/src/main/java/processing/data/IntDict.java @@ -3,6 +3,7 @@ import java.io.*; import java.util.HashMap; import java.util.Iterator; +import java.util.NoSuchElementException; import processing.core.PApplet; @@ -23,30 +24,7 @@ public class IntDict { protected int[] values; /** Internal implementation for faster lookups */ - private HashMap indices = new HashMap(); - - -// /** -// * Create a new object by counting the number of times each unique entry -// * shows up in the specified String array. -// */ -// static public IntHash fromTally(String[] list) { -// IntHash outgoing = new IntHash(); -// for (String s : list) { -// outgoing.inc(s); -// } -// outgoing.crop(); -// return outgoing; -// } -// -// -// static public IntHash fromOrder(String[] list) { -// IntHash outgoing = new IntHash(); -// for (int i = 0; i < list.length; i++) { -// outgoing.set(list[i], i); -// } -// return outgoing; -// } + private HashMap indices = new HashMap<>(); public IntDict() { @@ -76,18 +54,16 @@ public IntDict(int length) { * @nowebref */ public IntDict(BufferedReader reader) { -// public IntHash(PApplet parent, String filename) { String[] lines = PApplet.loadStrings(reader); keys = new String[lines.length]; values = new int[lines.length]; -// boolean csv = (lines[0].indexOf('\t') == -1); for (int i = 0; i < lines.length; i++) { -// String[] pieces = csv ? Table.splitLineCSV(lines[i]) : PApplet.split(lines[i], '\t'); String[] pieces = PApplet.split(lines[i], '\t'); if (pieces.length == 2) { keys[count] = pieces[0]; values[count] = PApplet.parseInt(pieces[1]); + indices.put(pieces[0], count); count++; } } @@ -108,6 +84,28 @@ public IntDict(String[] keys, int[] values) { } } + + /** + * Constructor to allow (more intuitive) inline initialization, e.g.: + *
    +   * new FloatDict(new Object[][] {
    +   *   { "key1", 1 },
    +   *   { "key2", 2 }
    +   * });
    +   * 
    + */ + public IntDict(Object[][] pairs) { + count = pairs.length; + this.keys = new String[count]; + this.values = new int[count]; + for (int i = 0; i < count; i++) { + keys[i] = (String) pairs[i][0]; + values[i] = (Integer) pairs[i][1]; + indices.put(keys[i], i); + } + } + + /** * Returns the number of key/value pairs * @@ -119,6 +117,29 @@ public int size() { } + /** + * Resize the internal data, this can only be used to shrink the list. + * Helpful for situations like sorting and then grabbing the top 50 entries. + */ + public void resize(int length) { + if (length > count) { + throw new IllegalArgumentException("resize() can only be used to shrink the dictionary"); + } + if (length < 1) { + throw new IllegalArgumentException("resize(" + length + ") is too small, use 1 or higher"); + } + + String[] newKeys = new String[length]; + int[] newValues = new int[length]; + PApplet.arrayCopy(keys, newKeys, length); + PApplet.arrayCopy(values, newValues, length); + keys = newKeys; + values = newValues; + count = length; + resetIndices(); + } + + /** * Remove all entries. * @@ -127,82 +148,86 @@ public int size() { */ public void clear() { count = 0; - indices = new HashMap(); + indices = new HashMap<>(); } + private void resetIndices() { + indices = new HashMap<>(count); + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public class Entry { + public String key; + public int value; + + Entry(String key, int value) { + this.key = key; + this.value = value; + } + } + + + public Iterable entries() { + return new Iterable() { + + public Iterator iterator() { + return entryIterator(); + } + }; + } + + + public Iterator entryIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Entry next() { + ++index; + Entry e = new Entry(keys[index], values[index]); + return e; + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + public String key(int index) { return keys[index]; } -// private void crop() { -// if (count != keys.length) { -// keys = PApplet.subset(keys, 0, count); -// values = PApplet.subset(values, 0, count); -// } -// } + protected void crop() { + if (count != keys.length) { + keys = PApplet.subset(keys, 0, count); + values = PApplet.subset(values, 0, count); + } + } - /** - * Return the internal array being used to store the keys. Allocated but - * unused entries will be removed. This array should not be modified. - * - * @webref intdict:method - * @brief Return the internal array being used to store the keys - */ -// public String[] keys() { -// crop(); -// return keys; -// } - - -// public Iterable keys() { -// return new Iterable() { -// -// @Override -// public Iterator iterator() { -// return new Iterator() { -// int index = -1; -// -// public void remove() { -// removeIndex(index); -// } -// -// public String next() { -// return key(++index); -// } -// -// public boolean hasNext() { -// return index+1 < size(); -// } -// }; -// } -// }; -// } - - - // Use this with 'for' loops public Iterable keys() { return new Iterable() { + @Override public Iterator iterator() { return keyIterator(); -// return new Iterator() { -// int index = -1; -// -// public void remove() { -// removeIndex(index); -// } -// -// public String next() { -// return key(++index); -// } -// -// public boolean hasNext() { -// return index+1 < size(); -// } -// }; } }; } @@ -215,6 +240,7 @@ public Iterator keyIterator() { public void remove() { removeIndex(index); + index--; } public String next() { @@ -235,6 +261,7 @@ public boolean hasNext() { * @brief Return a copy of the internal keys array */ public String[] keyArray() { + crop(); return keyArray(null); } @@ -255,11 +282,12 @@ public int value(int index) { /** * @webref intdict:method - * @brief Return the internal array being used to store the keys + * @brief Return the internal array being used to store the values */ public Iterable values() { return new Iterable() { + @Override public Iterator iterator() { return valueIterator(); } @@ -273,6 +301,7 @@ public Iterator valueIterator() { public void remove() { removeIndex(index); + index--; } public Integer next() { @@ -293,6 +322,7 @@ public boolean hasNext() { * @brief Create a new array and copy each of the values into it */ public int[] valueArray() { + crop(); return valueArray(null); } @@ -321,10 +351,20 @@ public int[] valueArray(int[] array) { */ public int get(String key) { int index = index(key); - if (index == -1) return 0; + if (index == -1) { + throw new IllegalArgumentException("No key named '" + key + "'"); + } + return values[index]; + } + + + public int get(String key, int alternate) { + int index = index(key); + if (index == -1) return alternate; return values[index]; } + /** * Create a new key/value pair or change the value of one. * @@ -340,6 +380,16 @@ public void set(String key, int amount) { } } + + public void setIndex(int index, String key, int value) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + keys[index] = key; + values[index] = value; + } + + /** * @webref intdict:method * @brief Check if a key is a part of the data structure @@ -360,6 +410,18 @@ public void increment(String key) { } + /** + * Merge another dictionary into this one. Calling this increment() + * since it doesn't make sense in practice for the other dictionary types, + * even though it's technically an add(). + */ + public void increment(IntDict dict) { + for (int i = 0; i < dict.count; i++) { + add(dict.key(i), dict.value(i)); + } + } + + /** * @webref intdict:method * @brief Add to a value @@ -419,7 +481,9 @@ private void checkMinMax(String functionName) { // return the index of the minimum value public int minIndex() { - checkMinMax("minIndex"); + //checkMinMax("minIndex"); + if (count == 0) return -1; + int index = 0; int value = values[0]; for (int i = 1; i < count; i++) { @@ -432,23 +496,30 @@ public int minIndex() { } - // return the minimum value - public int minValue() { - checkMinMax("minValue"); - return values[minIndex()]; - } - - // return the key for the minimum value public String minKey() { checkMinMax("minKey"); - return keys[minIndex()]; + int index = minIndex(); + if (index == -1) { + return null; + } + return keys[index]; + } + + + // return the minimum value, or throw an error if there are no values + public int minValue() { + checkMinMax("minValue"); + return values[minIndex()]; } // return the index of the max value public int maxIndex() { - checkMinMax("maxIndex"); + //checkMinMax("maxIndex"); + if (count == 0) { + return -1; + } int index = 0; int value = values[0]; for (int i = 1; i < count; i++) { @@ -461,17 +532,42 @@ public int maxIndex() { } - // return the maximum value + /** return the key corresponding to the maximum value or null if no entries */ + public String maxKey() { + //checkMinMax("maxKey"); + int index = maxIndex(); + if (index == -1) { + return null; + } + return keys[index]; + } + + + // return the maximum value or throw an error if zero length public int maxValue() { - checkMinMax("maxValue"); + checkMinMax("maxIndex"); return values[maxIndex()]; } - // return the key corresponding to the maximum value - public String maxKey() { - checkMinMax("maxKey"); - return keys[maxIndex()]; + public int sum() { + long amount = sumLong(); + if (amount > Integer.MAX_VALUE) { + throw new RuntimeException("sum() exceeds " + Integer.MAX_VALUE + ", use sumLong()"); + } + if (amount < Integer.MIN_VALUE) { + throw new RuntimeException("sum() less than " + Integer.MIN_VALUE + ", use sumLong()"); + } + return (int) amount; + } + + + public long sumLong() { + long sum = 0; + for (int i = 0; i < count; i++) { + sum += values[i]; + } + return sum; } @@ -486,7 +582,7 @@ protected void create(String what, int much) { keys = PApplet.expand(keys); values = PApplet.expand(values); } - indices.put(what, new Integer(count)); + indices.put(what, Integer.valueOf(count)); keys[count] = what; values[count] = much; count++; @@ -498,19 +594,20 @@ protected void create(String what, int much) { */ public int remove(String key) { int index = index(key); - if (index != -1) { - removeIndex(index); + if (index == -1) { + throw new NoSuchElementException("'" + key + "' not found"); } - return index; + int value = values[index]; + removeIndex(index); + return value; } - public String removeIndex(int index) { + public int removeIndex(int index) { if (index < 0 || index >= count) { throw new ArrayIndexOutOfBoundsException(index); } - //System.out.println("index is " + which + " and " + keys[which]); - String key = keys[index]; + int value = values[index]; indices.remove(keys[index]); for (int i = index; i < count-1; i++) { keys[i] = keys[i+1]; @@ -520,11 +617,11 @@ public String removeIndex(int index) { count--; keys[count] = null; values[count] = 0; - return key; + return value; } - protected void swap(int a, int b) { + public void swap(int a, int b) { String tkey = keys[a]; int tvalue = values[a]; keys[a] = keys[b]; @@ -532,8 +629,8 @@ protected void swap(int a, int b) { keys[b] = tkey; values[b] = tvalue; - indices.put(keys[a], new Integer(a)); - indices.put(keys[b], new Integer(b)); +// indices.put(keys[a], Integer.valueOf(a)); +// indices.put(keys[b], Integer.valueOf(b)); } @@ -545,7 +642,7 @@ protected void swap(int a, int b) { * @brief Sort the keys alphabetically */ public void sortKeys() { - sortImpl(true, false); + sortImpl(true, false, true); } /** @@ -553,28 +650,34 @@ public void sortKeys() { * tie-breaker (only really possible with a key that has a case change). * * @webref intdict:method - * @brief Sort the keys alphabetially in reverse + * @brief Sort the keys alphabetically in reverse */ public void sortKeysReverse() { - sortImpl(true, true); + sortImpl(true, true, true); } /** -<<<<<<< HEAD - * Sort by values in descending order (largest value will be at [0]). - * -======= * Sort by values in ascending order. The smallest value will be at [0]. * ->>>>>>> cd467dc12a42d588638aaab06746bebdfb333cc4 * @webref intdict:method * @brief Sort by values in ascending order */ public void sortValues() { - sortImpl(false, false); + sortValues(true); } + + /** + * Set true to ensure that the order returned is identical. Slightly + * slower because the tie-breaker for identical values compares the keys. + * @param stable + */ + public void sortValues(boolean stable) { + sortImpl(false, false, stable); + } + + /** * Sort by values in descending order. The largest value will be at [0]. * @@ -582,11 +685,17 @@ public void sortValues() { * @brief Sort by values in descending order */ public void sortValuesReverse() { - sortImpl(false, true); + sortValuesReverse(true); + } + + + public void sortValuesReverse(boolean stable) { + sortImpl(false, true, stable); } - protected void sortImpl(final boolean useKeys, final boolean reverse) { + protected void sortImpl(final boolean useKeys, final boolean reverse, + final boolean stable) { Sort s = new Sort() { @Override public int size() { @@ -594,16 +703,16 @@ public int size() { } @Override - public float compare(int a, int b) { + public int compare(int a, int b) { int diff = 0; if (useKeys) { diff = keys[a].compareToIgnoreCase(keys[b]); if (diff == 0) { - return values[a] - values[b]; + diff = values[a] - values[b]; } } else { // sort values diff = values[a] - values[b]; - if (diff == 0) { + if (diff == 0 && stable) { diff = keys[a].compareToIgnoreCase(keys[b]); } } @@ -616,19 +725,19 @@ public void swap(int a, int b) { } }; s.run(); + + // Set the indices after sort/swaps (performance fix 160411) + resetIndices(); } /** * Sum all of the values in this dictionary, then return a new FloatDict of * each key, divided by the total sum. The total for all values will be ~1.0. - * @return a Dict with the original keys, mapped to their pct of the total + * @return an IntDict with the original keys, mapped to their pct of the total */ public FloatDict getPercent() { - double sum = 0; - for (int value : valueArray()) { - sum += value; - } + double sum = sum(); // a little more accuracy FloatDict outgoing = new FloatDict(); for (int i = 0; i < size(); i++) { double percent = value(i) / sum; @@ -651,9 +760,25 @@ public IntDict copy() { } + public void print() { + for (int i = 0; i < size(); i++) { + System.out.println(keys[i] + " = " + values[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + /** - * Write tab-delimited entries out to - * @param writer + * Write tab-delimited entries to a PrintWriter */ public void write(PrintWriter writer) { for (int i = 0; i < count; i++) { @@ -663,17 +788,20 @@ public void write(PrintWriter writer) { } + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + StringList items = new StringList(); + for (int i = 0; i < count; i++) { + items.append(JSONObject.quote(keys[i])+ ": " + values[i]); + } + return "{ " + items.join(", ") + " }"; + } + + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName() + " size=" + size() + " { "); - for (int i = 0; i < size(); i++) { - if (i != 0) { - sb.append(", "); - } - sb.append("\"" + keys[i] + "\": " + values[i]); - } - sb.append(" }"); - return sb.toString(); + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } } diff --git a/core/src/processing/data/IntList.java b/libs/processing-core/src/main/java/processing/data/IntList.java similarity index 80% rename from core/src/processing/data/IntList.java rename to libs/processing-core/src/main/java/processing/data/IntList.java index 8e454a4a0..dc2c89916 100644 --- a/core/src/processing/data/IntList.java +++ b/libs/processing-core/src/main/java/processing/data/IntList.java @@ -1,5 +1,7 @@ package processing.data; +import java.io.File; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Iterator; import java.util.Random; @@ -33,6 +35,7 @@ public IntList() { data = new int[10]; } + /** * @nowebref */ @@ -40,6 +43,7 @@ public IntList(int length) { data = new int[length]; } + /** * @nowebref */ @@ -49,13 +53,48 @@ public IntList(int[] source) { System.arraycopy(source, 0, data, 0, count); } + /** + * Construct an IntList from an iterable pile of objects. + * For instance, a float array, an array of strings, who knows). + * Un-parseable or null values will be set to 0. * @nowebref */ - public IntList(Iterable iter) { + public IntList(Iterable iter) { this(10); - for (int v : iter) { - append(v); + for (Object o : iter) { + if (o == null) { + append(0); // missing value default + } else if (o instanceof Number) { + append(((Number) o).intValue()); + } else { + append(PApplet.parseInt(o.toString().trim())); + } + } + crop(); + } + + + /** + * Construct an IntList from a random pile of objects. + * Un-parseable or null values will be set to zero. + */ + public IntList(Object... items) { + final int missingValue = 0; // nuts, can't be last/final/second arg + + count = items.length; + data = new int[count]; + int index = 0; + for (Object o : items) { + int value = missingValue; + if (o != null) { + if (o instanceof Number) { + value = ((Number) o).intValue(); + } else { + value = PApplet.parseInt(o.toString().trim(), missingValue); + } + } + data[index++] = value; } } @@ -130,6 +169,9 @@ public void clear() { * @brief Get an entry at a particular index */ public int get(int index) { + if (index >= this.count) { + throw new ArrayIndexOutOfBoundsException(index); + } return data[index]; } @@ -154,6 +196,22 @@ public void set(int index, int what) { } + /** Just an alias for append(), but matches pop() */ + public void push(int value) { + append(value); + } + + + public int pop() { + if (count == 0) { + throw new RuntimeException("Can't call pop() on an empty list"); + } + int value = get(count-1); + count--; + return value; + } + + /** * Remove an element from the specified index * @@ -235,6 +293,14 @@ public void append(IntList list) { } + /** Add this value, but only if it's not already in the list. */ + public void appendUnique(int value) { + if (!hasValue(value)) { + append(value); + } + } + + // public void insert(int index, int value) { // if (index+1 > count) { // if (index+1 < data.length) { @@ -265,6 +331,11 @@ public void append(IntList list) { // } + public void insert(int index, int value) { + insert(index, new int[] { value }); + } + + // same as splice public void insert(int index, int[] values) { if (index < 0) { @@ -399,12 +470,24 @@ public void increment(int index) { data[index]++; } + + private void boundsProblem(int index, String method) { + final String msg = String.format("The list size is %d. " + + "You cannot %s() to element %d.", count, method, index); + throw new ArrayIndexOutOfBoundsException(msg); + } + + /** * @webref intlist:method * @brief Add to a value */ public void add(int index, int amount) { - data[index] += amount; + if (index < count) { + data[index] += amount; + } else { + boundsProblem(index, "add"); + } } /** @@ -412,7 +495,11 @@ public void add(int index, int amount) { * @brief Subtract from a value */ public void sub(int index, int amount) { - data[index] -= amount; + if (index < count) { + data[index] -= amount; + } else { + boundsProblem(index, "sub"); + } } /** @@ -420,7 +507,11 @@ public void sub(int index, int amount) { * @brief Multiply a value */ public void mult(int index, int amount) { - data[index] *= amount; + if (index < count) { + data[index] *= amount; + } else { + boundsProblem(index, "mult"); + } } /** @@ -428,7 +519,11 @@ public void mult(int index, int amount) { * @brief Divide a value */ public void div(int index, int amount) { - data[index] /= amount; + if (index < count) { + data[index] /= amount; + } else { + boundsProblem(index, "div"); + } } @@ -503,11 +598,23 @@ public int maxIndex() { public int sum() { - int outgoing = 0; + long amount = sumLong(); + if (amount > Integer.MAX_VALUE) { + throw new RuntimeException("sum() exceeds " + Integer.MAX_VALUE + ", use sumLong()"); + } + if (amount < Integer.MIN_VALUE) { + throw new RuntimeException("sum() less than " + Integer.MIN_VALUE + ", use sumLong()"); + } + return (int) amount; + } + + + public long sumLong() { + long sum = 0; for (int i = 0; i < count; i++) { - outgoing += data[i]; + sum += data[i]; } - return outgoing; + return sum; } @@ -536,7 +643,7 @@ public int size() { } @Override - public float compare(int a, int b) { + public int compare(int a, int b) { return data[b] - data[a]; } @@ -569,7 +676,7 @@ public void swap(int a, int b) { /** * @webref intlist:method - * @brief Reverse sort, orders values by first digit + * @brief Reverse the order of the list elements */ public void reverse() { int ii = count - 1; @@ -636,6 +743,7 @@ public int[] values() { } + @Override public Iterator iterator() { // public Iterator valueIterator() { return new Iterator() { @@ -643,6 +751,7 @@ public Iterator iterator() { public void remove() { IntList.this.remove(index); + index--; } public Integer next() { @@ -669,7 +778,8 @@ public int[] array() { /** - * Copy as many values as possible into the specified array. + * Copy values into the specified array. If the specified array is null or + * not the same size, a new array will be allocated. * @param array */ public int[] array(int[] array) { @@ -744,6 +854,19 @@ public FloatList getPercent() { } +// /** +// * Count the number of times each entry is found in this list. +// * Converts each entry to a String so it can be used as a key. +// */ +// public IntDict getTally() { +// IntDict outgoing = new IntDict(); +// for (int i = 0; i < count; i++) { +// outgoing.increment(String.valueOf(data[i])); +// } +// return outgoing; +// } + + public IntList getSubset(int start) { return getSubset(start, count - start); } @@ -770,17 +893,44 @@ public String join(String separator) { } + public void print() { + for (int i = 0; i < count; i++) { + System.out.format("[%d] %d%n", i, data[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write entries to a PrintWriter, one per line + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(data[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + return "[ " + join(", ") + " ]"; + } + + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName() + " size=" + size() + " [ "); - for (int i = 0; i < size(); i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(i + ": " + data[i]); - } - sb.append(" ]"); - return sb.toString(); + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } } diff --git a/core/src/processing/data/JSONArray.java b/libs/processing-core/src/main/java/processing/data/JSONArray.java similarity index 89% rename from core/src/processing/data/JSONArray.java rename to libs/processing-core/src/main/java/processing/data/JSONArray.java index c25ac93f3..ea8276bd8 100644 --- a/core/src/processing/data/JSONArray.java +++ b/libs/processing-core/src/main/java/processing/data/JSONArray.java @@ -36,7 +36,6 @@ of this software and associated documentation files (the "Software"), to deal import java.io.File; import java.io.IOException; -import java.io.OutputStream; import java.io.PrintWriter; import java.io.Reader; import java.io.StringWriter; @@ -110,7 +109,7 @@ public class JSONArray { * Construct an empty JSONArray. */ public JSONArray() { - this.myArrayList = new ArrayList(); + this.myArrayList = new ArrayList<>(); } @@ -126,7 +125,7 @@ public JSONArray(Reader reader) { * Construct a JSONArray from a JSONTokener. * * @param x A JSONTokener - * @throws JSONException If there is a syntax error. + * @throws RuntimeException If there is a syntax error. * @nowebref */ protected JSONArray(JSONTokener x) { @@ -166,9 +165,9 @@ protected JSONArray(JSONTokener x) { * @nowebref */ public JSONArray(IntList list) { - myArrayList = new ArrayList(); + myArrayList = new ArrayList<>(); for (int item : list.values()) { - myArrayList.add(new Integer(item)); + myArrayList.add(Integer.valueOf(item)); } } @@ -177,9 +176,9 @@ public JSONArray(IntList list) { * @nowebref */ public JSONArray(FloatList list) { - myArrayList = new ArrayList(); + myArrayList = new ArrayList<>(); for (float item : list.values()) { - myArrayList.add(new Float(item)); + myArrayList.add(Float.valueOf(item)); } } @@ -188,7 +187,7 @@ public JSONArray(FloatList list) { * @nowebref */ public JSONArray(StringList list) { - myArrayList = new ArrayList(); + myArrayList = new ArrayList<>(); for (String item : list.values()) { myArrayList.add(item); } @@ -200,7 +199,7 @@ public JSONArray(StringList list) { * @param source A string that begins with * [ (left bracket) * and ends with ] (right bracket). - * @throws JSONException If there is a syntax error. + * @return {@code null} if there is a syntax error. */ static public JSONArray parse(String source) { try { @@ -229,7 +228,7 @@ static public JSONArray parse(String source) { // TODO not decided whether we keep this one, but used heavily by JSONObject /** * Construct a JSONArray from an array - * @throws JSONException If not an array. + * @throws RuntimeException If not an array. */ protected JSONArray(Object array) { this(); @@ -262,9 +261,9 @@ private Object opt(int index) { * Get the object value associated with an index. * @param index must be between 0 and length() - 1 * @return An object value. - * @throws JSONException If there is no value for the index. + * @throws RuntimeException If there is no value for the index. */ - private Object get(int index) { + public Object get(int index) { Object object = opt(index); if (object == null) { throw new RuntimeException("JSONArray[" + index + "] not found."); @@ -280,7 +279,7 @@ private Object get(int index) { * @brief Gets the String value associated with an index * @param index must be between 0 and length() - 1 * @return A string value. - * @throws JSONException If there is no string value for the index. + * @throws RuntimeException If there is no string value for the index. * @see JSONArray#getInt(int) * @see JSONArray#getFloat(int) * @see JSONArray#getBoolean(int) @@ -315,7 +314,7 @@ public String getString(int index, String defaultValue) { * @brief Gets the int value associated with an index * @param index must be between 0 and length() - 1 * @return The value. - * @throws JSONException If the key is not found or if the value is not a number. + * @throws RuntimeException If the key is not found or if the value is not a number. * @see JSONArray#getFloat(int) * @see JSONArray#getString(int) * @see JSONArray#getBoolean(int) @@ -354,7 +353,7 @@ public int getInt(int index, int defaultValue) { * * @param index The index must be between 0 and length() - 1 * @return The value. - * @throws JSONException If the key is not found or if the value cannot + * @throws RuntimeException If the key is not found or if the value cannot * be converted to a number. */ public long getLong(int index) { @@ -416,7 +415,7 @@ public float getFloat(int index, float defaultValue) { * * @param index must be between 0 and length() - 1 * @return The value. - * @throws JSONException If the key is not found or if the value cannot + * @throws RuntimeException If the key is not found or if the value cannot * be converted to a number. */ public double getDouble(int index) { @@ -457,7 +456,7 @@ public double getDouble(int index, double defaultValue) { * @brief Gets the boolean value associated with an index * @param index must be between 0 and length() - 1 * @return The truth. - * @throws JSONException If there is no value for the index or if the + * @throws RuntimeException If there is no value for the index or if the * value is not convertible to boolean. * @see JSONArray#getInt(int) * @see JSONArray#getFloat(int) @@ -503,7 +502,7 @@ public boolean getBoolean(int index, boolean defaultValue) { * @brief Gets the JSONArray associated with an index value * @param index must be between 0 and length() - 1 * @return A JSONArray value. - * @throws JSONException If there is no value for the index. or if the + * @throws RuntimeException If there is no value for the index. or if the * value is not a JSONArray * @see JSONArray#getJSONObject(int) * @see JSONArray#setJSONObject(int, JSONObject) @@ -534,7 +533,7 @@ public JSONArray getJSONArray(int index, JSONArray defaultValue) { * @brief Gets the JSONObject associated with an index value * @param index the index value of the object to get * @return A JSONObject value. - * @throws JSONException If there is no value for the index or if the + * @throws RuntimeException If there is no value for the index or if the * value is not a JSONObject * @see JSONArray#getJSONArray(int) * @see JSONArray#setJSONObject(int, JSONObject) @@ -718,7 +717,7 @@ public JSONArray append(String value) { * @return this. */ public JSONArray append(int value) { - this.append(new Integer(value)); + this.append(Integer.valueOf(value)); return this; } @@ -731,7 +730,7 @@ public JSONArray append(int value) { * @return this. */ public JSONArray append(long value) { - this.append(new Long(value)); + this.append(Long.valueOf(value)); return this; } @@ -741,7 +740,7 @@ public JSONArray append(long value) { * This will store the value as a double, since there are no floats in JSON. * * @param value a float value - * @throws JSONException if the value is not finite. + * @throws RuntimeException if the value is not finite. * @return this. */ public JSONArray append(float value) { @@ -754,11 +753,11 @@ public JSONArray append(float value) { * * @nowebref * @param value A double value. - * @throws JSONException if the value is not finite. + * @throws RuntimeException if the value is not finite. * @return this. */ public JSONArray append(double value) { - Double d = new Double(value); + Double d = value; JSONObject.testValidity(d); this.append(d); return this; @@ -838,7 +837,7 @@ protected JSONArray append(Object value) { // * @param index The subscript. // * @param value A Collection value. // * @return this. -// * @throws JSONException If the index is negative or if the value is +// * @throws RuntimeException If the index is negative or if the value is // * not finite. // */ // public JSONArray set(int index, Collection value) { @@ -857,7 +856,7 @@ protected JSONArray append(Object value) { * @param index an index value * @param value the value to assign * @return this. - * @throws JSONException If the index is negative. + * @throws RuntimeException If the index is negative. * @see JSONArray#setInt(int, int) * @see JSONArray#setFloat(int, float) * @see JSONArray#setBoolean(int, boolean) @@ -878,13 +877,13 @@ public JSONArray setString(int index, String value) { * @param index an index value * @param value the value to assign * @return this. - * @throws JSONException If the index is negative. + * @throws RuntimeException If the index is negative. * @see JSONArray#setFloat(int, float) * @see JSONArray#setString(int, String) * @see JSONArray#setBoolean(int, boolean) */ public JSONArray setInt(int index, int value) { - this.set(index, new Integer(value)); + this.set(index, Integer.valueOf(value)); return this; } @@ -896,10 +895,10 @@ public JSONArray setInt(int index, int value) { * @param index The subscript. * @param value A long value. * @return this. - * @throws JSONException If the index is negative. + * @throws RuntimeException If the index is negative. */ public JSONArray setLong(int index, long value) { - return set(index, new Long(value)); + return set(index, Long.valueOf(value)); } @@ -932,11 +931,11 @@ public JSONArray setFloat(int index, float value) { * @param index The subscript. * @param value A double value. * @return this. - * @throws JSONException If the index is negative or if the value is + * @throws RuntimeException If the index is negative or if the value is * not finite. */ public JSONArray setDouble(int index, double value) { - return set(index, new Double(value)); + return set(index, Double.valueOf(value)); } @@ -950,7 +949,7 @@ public JSONArray setDouble(int index, double value) { * @param index an index value * @param value the value to assign * @return this. - * @throws JSONException If the index is negative. + * @throws RuntimeException If the index is negative. * @see JSONArray#setInt(int, int) * @see JSONArray#setFloat(int, float) * @see JSONArray#setString(int, String) @@ -966,7 +965,7 @@ public JSONArray setBoolean(int index, boolean value) { // * @param index The subscript. // * @param value The Map value. // * @return this. -// * @throws JSONException If the index is negative or if the the value is +// * @throws RuntimeException If the index is negative or if the the value is // * an invalid number. // */ // public JSONArray set(int index, Map value) { @@ -1012,7 +1011,7 @@ public JSONArray setJSONObject(int index, JSONObject value) { * Boolean, Double, Integer, JSONArray, JSONObject, Long, or String, or the * JSONObject.NULL object. * @return this. - * @throws JSONException If the index is negative or if the the value is + * @throws RuntimeException If the index is negative or if the the value is * an invalid number. */ private JSONArray set(int index, Object value) { @@ -1048,6 +1047,7 @@ public int size() { /** * Determine if the value is null. + * @webref * @param index must be between 0 and length() - 1 * @return true if the value at the index is null, or if there is no value. */ @@ -1094,18 +1094,42 @@ public Object remove(int index) { // } - protected boolean save(OutputStream output) { - return save(PApplet.createWriter(output)); - } +// protected boolean save(OutputStream output) { +// return write(PApplet.createWriter(output), null); +// } public boolean save(File file, String options) { - return save(PApplet.createWriter(file)); + PrintWriter writer = PApplet.createWriter(file); + boolean success = write(writer, options); + writer.close(); + return success; + } + + + public boolean write(PrintWriter output) { + return write(output, null); } - public boolean save(PrintWriter output) { - output.print(format(2)); + public boolean write(PrintWriter output, String options) { + int indentFactor = 2; + if (options != null) { + String[] opts = PApplet.split(options, ','); + for (String opt : opts) { + if (opt.equals("compact")) { + indentFactor = -1; + } else if (opt.startsWith("indent=")) { + indentFactor = PApplet.parseInt(opt.substring(7), -2); + if (indentFactor == -2) { + throw new IllegalArgumentException("Could not read a number from " + opt); + } + } else { + System.err.println("Ignoring " + opt); + } + } + } + output.print(format(indentFactor)); output.flush(); return true; } @@ -1139,27 +1163,26 @@ public String toString() { public String format(int indentFactor) { StringWriter sw = new StringWriter(); synchronized (sw.getBuffer()) { - return this.write(sw, indentFactor, 0).toString(); + return this.writeInternal(sw, indentFactor, 0).toString(); } } - /** - * Write the contents of the JSONArray as JSON text to a writer. For - * compactness, no whitespace is added. - *

    - * Warning: This method assumes that the data structure is acyclic. - * - * @return The writer. - */ - protected Writer write(Writer writer) { - return this.write(writer, -1, 0); - } +// /** +// * Write the contents of the JSONArray as JSON text to a writer. For +// * compactness, no whitespace is added. +// *

    +// * Warning: This method assumes that the data structure is acyclic. +// * +// * @return The writer. +// */ +// protected Writer write(Writer writer) { +// return this.write(writer, -1, 0); +// } /** - * Write the contents of the JSONArray as JSON text to a writer. For - * compactness, no whitespace is added. + * Write the contents of the JSONArray as JSON text to a writer. *

    * Warning: This method assumes that the data structure is acyclic. * @@ -1169,9 +1192,9 @@ protected Writer write(Writer writer) { * @param indent * The indention of the top level. * @return The writer. - * @throws JSONException + * @throws RuntimeException */ - protected Writer write(Writer writer, int indentFactor, int indent) { + protected Writer writeInternal(Writer writer, int indentFactor, int indent) { try { boolean commanate = false; int length = this.size(); @@ -1182,9 +1205,10 @@ protected Writer write(Writer writer, int indentFactor, int indent) { if (length == 1) { JSONObject.writeValue(writer, this.myArrayList.get(0), - thisFactor, indent); + indentFactor, indent); +// thisFactor, indent); } else if (length != 0) { - final int newindent = indent + thisFactor; + final int newIndent = indent + thisFactor; for (int i = 0; i < length; i += 1) { if (commanate) { @@ -1193,9 +1217,11 @@ protected Writer write(Writer writer, int indentFactor, int indent) { if (indentFactor != -1) { writer.write('\n'); } - JSONObject.indent(writer, newindent); + JSONObject.indent(writer, newIndent); +// JSONObject.writeValue(writer, this.myArrayList.get(i), +// thisFactor, newIndent); JSONObject.writeValue(writer, this.myArrayList.get(i), - thisFactor, newindent); + indentFactor, newIndent); commanate = true; } if (indentFactor != -1) { @@ -1217,11 +1243,11 @@ protected Writer write(Writer writer, int indentFactor, int indent) { * Warning: This method assumes that the data structure is acyclic. * @param separator A string that will be inserted between the elements. * @return a string. - * @throws JSONException If the array contains an invalid number. + * @throws RuntimeException If the array contains an invalid number. */ public String join(String separator) { int len = this.size(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (int i = 0; i < len; i += 1) { if (i > 0) { diff --git a/core/src/processing/data/JSONObject.java b/libs/processing-core/src/main/java/processing/data/JSONObject.java similarity index 92% rename from core/src/processing/data/JSONObject.java rename to libs/processing-core/src/main/java/processing/data/JSONObject.java index 9b1387d4f..cc7a22de0 100644 --- a/core/src/processing/data/JSONObject.java +++ b/libs/processing-core/src/main/java/processing/data/JSONObject.java @@ -50,7 +50,6 @@ of this software and associated documentation files (the "Software"), to deal import processing.core.PApplet; - /** * A JSONObject is an unordered collection of name/value pairs. Its external * form is a string wrapped in curly braces with colons between the names and @@ -96,7 +95,7 @@ of this software and associated documentation files (the "Software"), to deal * { } [ ] / \ : , = ; # and if they do not look like numbers and * if they are not the reserved words true, false, or * null. - *

  • Keys can be followed by = or => as well as by + *
  • Keys can be followed by = or {@code =>} as well as by * :.
  • *
  • Values can be followed by ; (semicolon) as * well as by , (comma).
  • @@ -111,7 +110,6 @@ of this software and associated documentation files (the "Software"), to deal * @see PApplet#saveJSONObject(JSONObject, String) * @see PApplet#saveJSONArray(JSONArray, String) */ -@SuppressWarnings("rawtypes") public class JSONObject { /** * The maximum number of keys in the key pool. @@ -125,7 +123,7 @@ public class JSONObject { * string objects. This is used by JSONObject.put(string, object). */ private static HashMap keyPool = - new HashMap(keyPoolSize); + new HashMap<>(keyPoolSize); // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -199,7 +197,7 @@ public int hashCode() { * @nowebref */ public JSONObject() { - this.map = new HashMap(); + this.map = new HashMap<>(); } @@ -209,8 +207,6 @@ public JSONObject() { // * Missing keys are ignored. // * @param jo A JSONObject. // * @param names An array of strings. -// * @throws JSONException -// * @exception JSONException If a value is a non-finite number or if a name is duplicated. // */ // public JSONObject(JSONObject jo, String[] names) { // this(); @@ -234,7 +230,7 @@ public JSONObject(Reader reader) { /** * Construct a JSONObject from a JSONTokener. * @param x A JSONTokener object containing the source string. - * @throws JSONException If there is a syntax error in the source string + * @throws RuntimeException If there is a syntax error in the source string * or a duplicated key. */ protected JSONObject(JSONTokener x) { @@ -293,10 +289,9 @@ protected JSONObject(JSONTokener x) { * * @param map A map object that can be used to initialize the contents of * the JSONObject. - * @throws JSONException */ protected JSONObject(HashMap map) { - this.map = new HashMap(); + this.map = new HashMap<>(); if (map != null) { Iterator i = map.entrySet().iterator(); while (i.hasNext()) { @@ -314,7 +309,7 @@ protected JSONObject(HashMap map) { * @nowebref */ public JSONObject(IntDict dict) { - map = new HashMap(); + map = new HashMap<>(); for (int i = 0; i < dict.size(); i++) { setInt(dict.key(i), dict.value(i)); } @@ -325,7 +320,7 @@ public JSONObject(IntDict dict) { * @nowebref */ public JSONObject(FloatDict dict) { - map = new HashMap(); + map = new HashMap<>(); for (int i = 0; i < dict.size(); i++) { setFloat(dict.key(i), dict.value(i)); } @@ -336,7 +331,7 @@ public JSONObject(FloatDict dict) { * @nowebref */ public JSONObject(StringDict dict) { - map = new HashMap(); + map = new HashMap<>(); for (int i = 0; i < dict.size(); i++) { setString(dict.key(i), dict.value(i)); } @@ -399,7 +394,7 @@ protected JSONObject(Object bean) { * @param source A string beginning * with { (left brace) and ending * with } (right brace). - * @exception JSONException If there is a syntax error in the source + * @exception RuntimeException If there is a syntax error in the source * string or a duplicated key. */ static public JSONObject parse(String source) { @@ -540,15 +535,19 @@ static protected String doubleToString(double d) { * * @param key A key string. * @return The object associated with the key. - * @throws JSONException if the key is not found. + * @throws RuntimeException if the key is not found. */ - private Object get(String key) { + public Object get(String key) { if (key == null) { - throw new RuntimeException("Null key."); + throw new RuntimeException("JSONObject.get(null) called"); } Object object = this.opt(key); if (object == null) { - throw new RuntimeException("JSONObject[" + quote(key) + "] not found."); + // Adding for rev 0257 in line with other p5 api + return null; + } + if (object == null) { + throw new RuntimeException("JSONObject[" + quote(key) + "] not found"); } return object; } @@ -561,17 +560,21 @@ private Object get(String key) { * @brief Gets the string value associated with a key * @param key a key string * @return A string which is the value. - * @throws JSONException if there is no string value for the key. + * @throws RuntimeException if there is no string value for the key. * @see JSONObject#getInt(String) * @see JSONObject#getFloat(String) * @see JSONObject#getBoolean(String) */ public String getString(String key) { Object object = this.get(key); + if (object == null) { + // Adding for rev 0257 in line with other p5 api + return null; + } if (object instanceof String) { return (String)object; } - throw new RuntimeException("JSONObject[" + quote(key) + "] not a string."); + throw new RuntimeException("JSONObject[" + quote(key) + "] is not a string"); } @@ -594,9 +597,9 @@ public String getString(String key, String defaultValue) { * * @webref jsonobject:method * @brief Gets the int value associated with a key - * @param key a key string + * @param key A key string. * @return The integer value. - * @throws JSONException if the key is not found or if the value cannot + * @throws RuntimeException if the key is not found or if the value cannot * be converted to an integer. * @see JSONObject#getFloat(String) * @see JSONObject#getString(String) @@ -604,10 +607,12 @@ public String getString(String key, String defaultValue) { */ public int getInt(String key) { Object object = this.get(key); + if (object == null) { + throw new RuntimeException("JSONObject[" + quote(key) + "] not found"); + } try { - return object instanceof Number - ? ((Number)object).intValue() - : Integer.parseInt((String)object); + return object instanceof Number ? + ((Number)object).intValue() : Integer.parseInt((String)object); } catch (Exception e) { throw new RuntimeException("JSONObject[" + quote(key) + "] is not an int."); } @@ -638,7 +643,7 @@ public int getInt(String key, int defaultValue) { * * @param key A key string. * @return The long value. - * @throws JSONException if the key is not found or if the value cannot + * @throws RuntimeException if the key is not found or if the value cannot * be converted to a long. */ public long getLong(String key) { @@ -698,7 +703,7 @@ public float getFloat(String key, float defaultValue) { * Get the double value associated with a key. * @param key A key string. * @return The numeric value. - * @throws JSONException if the key is not found or + * @throws RuntimeException if the key is not found or * if the value is not a Number object and cannot be converted to a number. */ public double getDouble(String key) { @@ -739,7 +744,7 @@ public double getDouble(String key, double defaultValue) { * @brief Gets the boolean value associated with a key * @param key a key string * @return The truth. - * @throws JSONException if the value is not a Boolean or the String "true" or "false". + * @throws RuntimeException if the value is not a Boolean or the String "true" or "false". * @see JSONObject#getInt(String) * @see JSONObject#getFloat(String) * @see JSONObject#getString(String) @@ -783,14 +788,17 @@ public boolean getBoolean(String key, boolean defaultValue) { * @webref jsonobject:method * @brief Gets the JSONArray value associated with a key * @param key a key string - * @return A JSONArray which is the value. - * @throws JSONException if the key is not found or if the value is not a JSONArray. + * @return A JSONArray which is the value, or null if not present + * @throws RuntimeException if the value is not a JSONArray. * @see JSONObject#getJSONObject(String) * @see JSONObject#setJSONObject(String, JSONObject) * @see JSONObject#setJSONArray(String, JSONArray) */ public JSONArray getJSONArray(String key) { Object object = this.get(key); + if (object == null) { + return null; + } if (object instanceof JSONArray) { return (JSONArray)object; } @@ -804,14 +812,17 @@ public JSONArray getJSONArray(String key) { * @webref jsonobject:method * @brief Gets the JSONObject value associated with a key * @param key a key string - * @return A JSONObject which is the value. - * @throws JSONException if the key is not found or if the value is not a JSONObject. + * @return A JSONObject which is the value or null if not available. + * @throws RuntimeException if the value is not a JSONObject. * @see JSONObject#getJSONArray(String) * @see JSONObject#setJSONObject(String, JSONObject) * @see JSONObject#setJSONArray(String, JSONArray) */ public JSONObject getJSONObject(String key) { Object object = this.get(key); + if (object == null) { + return null; + } if (object instanceof JSONObject) { return (JSONObject)object; } @@ -869,7 +880,7 @@ public JSONObject getJSONObject(String key) { * @return true if the key exists in the JSONObject. */ public boolean hasKey(String key) { - return this.map.containsKey(key); + return map.containsKey(key); } @@ -903,7 +914,9 @@ public boolean hasKey(String key) { /** * Determine if the value associated with the key is null or if there is - * no value. + * no value. + * + * @webref * @param key A key string. * @return true if there is no value associated with the key or if * the value is the JSONObject.NULL object. @@ -964,7 +977,7 @@ public int size() { * Produce a string from a Number. * @param number A Number * @return A String. - * @throws JSONException If n is a non-finite number. + * @throws RuntimeException If number is null or a non-finite number. */ private static String numberToString(Number number) { if (number == null) { @@ -1164,13 +1177,13 @@ public JSONObject setString(String key, String value) { * @param key a key string * @param value the value to assign * @return this. - * @throws JSONException If the key is null. + * @throws RuntimeException If the key is null. * @see JSONObject#setFloat(String, float) * @see JSONObject#setString(String, String) * @see JSONObject#setBoolean(String, boolean) */ public JSONObject setInt(String key, int value) { - this.put(key, new Integer(value)); + this.put(key, Integer.valueOf(value)); return this; } @@ -1181,10 +1194,10 @@ public JSONObject setInt(String key, int value) { * @param key A key string. * @param value A long which is the value. * @return this. - * @throws JSONException If the key is null. + * @throws RuntimeException If the key is null. */ public JSONObject setLong(String key, long value) { - this.put(key, new Long(value)); + this.put(key, Long.valueOf(value)); return this; } @@ -1193,12 +1206,13 @@ public JSONObject setLong(String key, long value) { * @brief Put a key/float pair in the JSONObject * @param key a key string * @param value the value to assign + * @throws RuntimeException If the key is null or if the number is NaN or infinite. * @see JSONObject#setInt(String, int) * @see JSONObject#setString(String, String) * @see JSONObject#setBoolean(String, boolean) */ public JSONObject setFloat(String key, float value) { - this.put(key, new Double(value)); + this.put(key, Double.valueOf(value)); return this; } @@ -1209,10 +1223,10 @@ public JSONObject setFloat(String key, float value) { * @param key A key string. * @param value A double which is the value. * @return this. - * @throws JSONException If the key is null or if the number is invalid. + * @throws RuntimeException If the key is null or if the number is NaN or infinite. */ public JSONObject setDouble(String key, double value) { - this.put(key, new Double(value)); + this.put(key, Double.valueOf(value)); return this; } @@ -1225,7 +1239,7 @@ public JSONObject setDouble(String key, double value) { * @param key a key string * @param value the value to assign * @return this. - * @throws JSONException If the key is null. + * @throws RuntimeException If the key is null. * @see JSONObject#setInt(String, int) * @see JSONObject#setFloat(String, float) * @see JSONObject#setString(String, String) @@ -1299,10 +1313,10 @@ public JSONObject setJSONArray(String key, JSONArray value) { * types: Boolean, Double, Integer, JSONArray, JSONObject, Long, String, * or the JSONObject.NULL object. * @return this. - * @throws JSONException If the value is non-finite number + * @throws RuntimeException If the value is non-finite number * or if the key is null. */ - private JSONObject put(String key, Object value) { + public JSONObject put(String key, Object value) { String pooled; if (key == null) { throw new RuntimeException("Null key."); @@ -1312,7 +1326,7 @@ private JSONObject put(String key, Object value) { pooled = (String)keyPool.get(key); if (pooled == null) { if (keyPool.size() >= keyPoolSize) { - keyPool = new HashMap(keyPoolSize); + keyPool = new HashMap<>(keyPoolSize); } keyPool.put(key, key); } else { @@ -1332,8 +1346,9 @@ private JSONObject put(String key, Object value) { * with that name. * @param key * @param value - * @return his. - * @throws JSONException if the key is a duplicate + * @return {@code this}. + * @throws RuntimeException if the key is a duplicate, or if + * {@link #put(String,Object)} throws. */ private JSONObject putOnce(String key, Object value) { if (key != null && value != null) { @@ -1372,7 +1387,7 @@ private JSONObject putOnce(String key, Object value) { * @param string A String * @return A String correctly formatted for insertion in a JSON text. */ - static protected String quote(String string) { + static public String quote(String string) { StringWriter sw = new StringWriter(); synchronized (sw.getBuffer()) { try { @@ -1384,7 +1399,7 @@ static protected String quote(String string) { } } - static protected Writer quote(String string, Writer w) throws IOException { + static public Writer quote(String string, Writer w) throws IOException { if (string == null || string.length() == 0) { w.write("\"\""); return w; @@ -1494,9 +1509,9 @@ static protected Object stringToValue(String string) { return d; } } else { - Long myLong = new Long(string); + Long myLong = Long.valueOf(string); if (myLong.longValue() == myLong.intValue()) { - return new Integer(myLong.intValue()); + return Integer.valueOf(myLong.intValue()); } else { return myLong; } @@ -1510,8 +1525,9 @@ static protected Object stringToValue(String string) { /** * Throw an exception if the object is a NaN or infinite number. - * @param o The object to test. - * @throws JSONException If o is a non-finite number. + * @param o The object to test. If not Float or Double, accepted without + * exceptions. + * @throws RuntimeException If o is infinite or NaN. */ static protected void testValidity(Object o) { if (o != null) { @@ -1556,12 +1572,36 @@ static protected void testValidity(Object o) { public boolean save(File file, String options) { - return write(PApplet.createWriter(file)); + PrintWriter writer = PApplet.createWriter(file); + boolean success = write(writer, options); + writer.close(); + return success; } public boolean write(PrintWriter output) { - output.print(format(2)); + return write(output, null); + } + + + public boolean write(PrintWriter output, String options) { + int indentFactor = 2; + if (options != null) { + String[] opts = PApplet.split(options, ','); + for (String opt : opts) { + if (opt.equals("compact")) { + indentFactor = -1; + } else if (opt.startsWith("indent=")) { + indentFactor = PApplet.parseInt(opt.substring(7), -2); + if (indentFactor == -2) { + throw new IllegalArgumentException("Could not read a number from " + opt); + } + } else { + System.err.println("Ignoring " + opt); + } + } + } + output.print(format(indentFactor)); output.flush(); return true; } @@ -1592,12 +1632,12 @@ public String toString() { * representation of the object, beginning * with { (left brace) and ending * with } (right brace). - * @throws JSONException If the object contains an invalid number. + * @throws RuntimeException If the object contains an invalid number. */ public String format(int indentFactor) { StringWriter w = new StringWriter(); synchronized (w.getBuffer()) { - return this.write(w, indentFactor, 0).toString(); + return this.writeInternal(w, indentFactor, 0).toString(); } } @@ -1620,7 +1660,7 @@ public String format(int indentFactor) { * representation of the object, beginning * with { (left brace) and ending * with } (right brace). - * @throws JSONException If the value is or contains an invalid number. + * @throws RuntimeException If the value is or contains an invalid number. */ static protected String valueToString(Object value) { if (value == null || value.equals(null)) { @@ -1646,10 +1686,10 @@ static protected String valueToString(Object value) { return value.toString(); } if (value instanceof Map) { - return new JSONObject((Map)value).toString(); + return new JSONObject(value).toString(); } if (value instanceof Collection) { - return new JSONArray((Collection)value).toString(); + return new JSONArray(value).toString(); } if (value.getClass().isArray()) { return new JSONArray(value).toString(); @@ -1685,13 +1725,13 @@ static protected Object wrap(Object object) { } if (object instanceof Collection) { - return new JSONArray((Collection)object); + return new JSONArray(object); } if (object.getClass().isArray()) { return new JSONArray(object); } if (object instanceof Map) { - return new JSONObject((Map)object); + return new JSONObject(object); } Package objectPackage = object.getClass().getPackage(); String objectPackageName = objectPackage != null @@ -1730,16 +1770,16 @@ static final Writer writeValue(Writer writer, Object value, if (value == null || value.equals(null)) { writer.write("null"); } else if (value instanceof JSONObject) { - ((JSONObject) value).write(writer, indentFactor, indent); + ((JSONObject) value).writeInternal(writer, indentFactor, indent); } else if (value instanceof JSONArray) { - ((JSONArray) value).write(writer, indentFactor, indent); + ((JSONArray) value).writeInternal(writer, indentFactor, indent); } else if (value instanceof Map) { - new JSONObject((Map) value).write(writer, indentFactor, indent); + new JSONObject(value).writeInternal(writer, indentFactor, indent); } else if (value instanceof Collection) { - new JSONArray((Collection) value).write(writer, indentFactor, + new JSONArray(value).writeInternal(writer, indentFactor, indent); } else if (value.getClass().isArray()) { - new JSONArray(value).write(writer, indentFactor, indent); + new JSONArray(value).writeInternal(writer, indentFactor, indent); } else if (value instanceof Number) { writer.write(numberToString((Number) value)); } else if (value instanceof Boolean) { @@ -1768,15 +1808,14 @@ static final void indent(Writer writer, int indent) throws IOException { } /** - * Write the contents of the JSONObject as JSON text to a writer. For - * compactness, no whitespace is added. + * Write the contents of the JSONObject as JSON text to a writer. *

    * Warning: This method assumes that the data structure is acyclical. * * @return The writer. - * @throws JSONException + * @throws RuntimeException */ - protected Writer write(Writer writer, int indentFactor, int indent) { + protected Writer writeInternal(Writer writer, int indentFactor, int indent) { try { boolean commanate = false; final int length = this.size(); @@ -1792,9 +1831,10 @@ protected Writer write(Writer writer, int indentFactor, int indent) { if (actualFactor > 0) { writer.write(' '); } - writeValue(writer, this.map.get(key), actualFactor, indent); + //writeValue(writer, this.map.get(key), actualFactor, indent); + writeValue(writer, this.map.get(key), indentFactor, indent); } else if (length != 0) { - final int newindent = indent + actualFactor; + final int newIndent = indent + actualFactor; while (keys.hasNext()) { Object key = keys.next(); if (commanate) { @@ -1803,14 +1843,14 @@ protected Writer write(Writer writer, int indentFactor, int indent) { if (indentFactor != -1) { writer.write('\n'); } - indent(writer, newindent); + indent(writer, newIndent); writer.write(quote(key.toString())); writer.write(':'); if (actualFactor > 0) { writer.write(' '); } - writeValue(writer, this.map.get(key), actualFactor, - newindent); + //writeValue(writer, this.map.get(key), actualFactor, newIndent); + writeValue(writer, this.map.get(key), indentFactor, newIndent); commanate = true; } if (indentFactor != -1) { diff --git a/core/src/processing/data/JSONTokener.java b/libs/processing-core/src/main/java/processing/data/JSONTokener.java similarity index 98% rename from core/src/processing/data/JSONTokener.java rename to libs/processing-core/src/main/java/processing/data/JSONTokener.java index 499f10a82..5301a5e3c 100644 --- a/core/src/processing/data/JSONTokener.java +++ b/libs/processing-core/src/main/java/processing/data/JSONTokener.java @@ -249,7 +249,7 @@ public char nextClean() { */ public String nextString(char quote) { char c; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { c = this.next(); switch (c) { @@ -305,7 +305,7 @@ public String nextString(char quote) { * @return A string. */ public String nextTo(char delimiter) { - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { char c = this.next(); if (c == delimiter || c == 0 || c == '\n' || c == '\r') { @@ -327,7 +327,7 @@ public String nextTo(char delimiter) { */ public String nextTo(String delimiters) { char c; - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); for (;;) { c = this.next(); if (delimiters.indexOf(c) >= 0 || c == 0 || @@ -374,7 +374,7 @@ public Object nextValue() { * formatting character. */ - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); while (c >= ' ' && ",:]}/\\\"[{;=#".indexOf(c) < 0) { sb.append(c); c = this.next(); diff --git a/libs/processing-core/src/main/java/processing/data/LongDict.java b/libs/processing-core/src/main/java/processing/data/LongDict.java new file mode 100644 index 000000000..529246862 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/data/LongDict.java @@ -0,0 +1,802 @@ +package processing.data; + +import java.io.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import processing.core.PApplet; + + +/** + * A simple class to use a String as a lookup for an int value. + * + * @webref data:composite + * @see FloatDict + * @see StringDict + */ +public class LongDict { + + /** Number of elements in the table */ + protected int count; + + protected String[] keys; + protected long[] values; + + /** Internal implementation for faster lookups */ + private HashMap indices = new HashMap<>(); + + + public LongDict() { + count = 0; + keys = new String[10]; + values = new long[10]; + } + + + /** + * Create a new lookup with a specific size. This is more efficient than not + * specifying a size. Use it when you know the rough size of the thing you're creating. + * + * @nowebref + */ + public LongDict(int length) { + count = 0; + keys = new String[length]; + values = new long[length]; + } + + + /** + * Read a set of entries from a Reader that has each key/value pair on + * a single line, separated by a tab. + * + * @nowebref + */ + public LongDict(BufferedReader reader) { + String[] lines = PApplet.loadStrings(reader); + keys = new String[lines.length]; + values = new long[lines.length]; + + for (int i = 0; i < lines.length; i++) { + String[] pieces = PApplet.split(lines[i], '\t'); + if (pieces.length == 2) { + keys[count] = pieces[0]; + values[count] = PApplet.parseInt(pieces[1]); + indices.put(pieces[0], count); + count++; + } + } + } + + /** + * @nowebref + */ + public LongDict(String[] keys, long[] values) { + if (keys.length != values.length) { + throw new IllegalArgumentException("key and value arrays must be the same length"); + } + this.keys = keys; + this.values = values; + count = keys.length; + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + /** + * Constructor to allow (more intuitive) inline initialization, e.g.: + *

    +   * new FloatDict(new Object[][] {
    +   *   { "key1", 1 },
    +   *   { "key2", 2 }
    +   * });
    +   * 
    + */ + public LongDict(Object[][] pairs) { + count = pairs.length; + this.keys = new String[count]; + this.values = new long[count]; + for (int i = 0; i < count; i++) { + keys[i] = (String) pairs[i][0]; + values[i] = (Integer) pairs[i][1]; + indices.put(keys[i], i); + } + } + + + /** + * Returns the number of key/value pairs + * + * @webref intdict:method + * @brief Returns the number of key/value pairs + */ + public int size() { + return count; + } + + + /** + * Resize the internal data, this can only be used to shrink the list. + * Helpful for situations like sorting and then grabbing the top 50 entries. + */ + public void resize(int length) { + if (length > count) { + throw new IllegalArgumentException("resize() can only be used to shrink the dictionary"); + } + if (length < 1) { + throw new IllegalArgumentException("resize(" + length + ") is too small, use 1 or higher"); + } + + String[] newKeys = new String[length]; + long[] newValues = new long[length]; + PApplet.arrayCopy(keys, newKeys, length); + PApplet.arrayCopy(values, newValues, length); + keys = newKeys; + values = newValues; + count = length; + resetIndices(); + } + + + /** + * Remove all entries. + * + * @webref intdict:method + * @brief Remove all entries + */ + public void clear() { + count = 0; + indices = new HashMap<>(); + } + + + private void resetIndices() { + indices = new HashMap<>(count); + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public class Entry { + public String key; + public long value; + + Entry(String key, long value) { + this.key = key; + this.value = value; + } + } + + + public Iterable entries() { + return new Iterable() { + + public Iterator iterator() { + return entryIterator(); + } + }; + } + + + public Iterator entryIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Entry next() { + ++index; + Entry e = new Entry(keys[index], values[index]); + return e; + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public String key(int index) { + return keys[index]; + } + + + protected void crop() { + if (count != keys.length) { + keys = PApplet.subset(keys, 0, count); + values = PApplet.subset(values, 0, count); + } + } + + + public Iterable keys() { + return new Iterable() { + + @Override + public Iterator iterator() { + return keyIterator(); + } + }; + } + + + // Use this to iterate when you want to be able to remove elements along the way + public Iterator keyIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public String next() { + return key(++index); + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + /** + * Return a copy of the internal keys array. This array can be modified. + * + * @webref intdict:method + * @brief Return a copy of the internal keys array + */ + public String[] keyArray() { + crop(); + return keyArray(null); + } + + + public String[] keyArray(String[] outgoing) { + if (outgoing == null || outgoing.length != count) { + outgoing = new String[count]; + } + System.arraycopy(keys, 0, outgoing, 0, count); + return outgoing; + } + + + public long value(int index) { + return values[index]; + } + + + /** + * @webref intdict:method + * @brief Return the internal array being used to store the values + */ + public Iterable values() { + return new Iterable() { + + @Override + public Iterator iterator() { + return valueIterator(); + } + }; + } + + + public Iterator valueIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Long next() { + return value(++index); + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + /** + * Create a new array and copy each of the values into it. + * + * @webref intdict:method + * @brief Create a new array and copy each of the values into it + */ + public int[] valueArray() { + crop(); + return valueArray(null); + } + + + /** + * Fill an already-allocated array with the values (more efficient than + * creating a new array each time). If 'array' is null, or not the same + * size as the number of values, a new array will be allocated and returned. + * + * @param array values to copy into the array + */ + public int[] valueArray(int[] array) { + if (array == null || array.length != size()) { + array = new int[count]; + } + System.arraycopy(values, 0, array, 0, count); + return array; + } + + + /** + * Return a value for the specified key. + * + * @webref intdict:method + * @brief Return a value for the specified key + */ + public long get(String key) { + int index = index(key); + if (index == -1) { + throw new IllegalArgumentException("No key named '" + key + "'"); + } + return values[index]; + } + + + public long get(String key, long alternate) { + int index = index(key); + if (index == -1) return alternate; + return values[index]; + } + + + /** + * Create a new key/value pair or change the value of one. + * + * @webref intdict:method + * @brief Create a new key/value pair or change the value of one + */ + public void set(String key, long amount) { + int index = index(key); + if (index == -1) { + create(key, amount); + } else { + values[index] = amount; + } + } + + + public void setIndex(int index, String key, long value) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + keys[index] = key; + values[index] = value; + } + + + /** + * @webref intdict:method + * @brief Check if a key is a part of the data structure + */ + public boolean hasKey(String key) { + return index(key) != -1; + } + + + /** + * Increase the value associated with a specific key by 1. + * + * @webref intdict:method + * @brief Increase the value of a specific key value by 1 + */ + public void increment(String key) { + add(key, 1); + } + + + /** + * Merge another dictionary into this one. Calling this increment() + * since it doesn't make sense in practice for the other dictionary types, + * even though it's technically an add(). + */ + public void increment(LongDict dict) { + for (int i = 0; i < dict.count; i++) { + add(dict.key(i), dict.value(i)); + } + } + + + /** + * @webref intdict:method + * @brief Add to a value + */ + public void add(String key, long amount) { + int index = index(key); + if (index == -1) { + create(key, amount); + } else { + values[index] += amount; + } + } + + + /** + * @webref intdict:method + * @brief Subtract from a value + */ + public void sub(String key, long amount) { + add(key, -amount); + } + + + /** + * @webref intdict:method + * @brief Multiply a value + */ + public void mult(String key, long amount) { + int index = index(key); + if (index != -1) { + values[index] *= amount; + } + } + + + /** + * @webref intdict:method + * @brief Divide a value + */ + public void div(String key, long amount) { + int index = index(key); + if (index != -1) { + values[index] /= amount; + } + } + + + private void checkMinMax(String functionName) { + if (count == 0) { + String msg = + String.format("Cannot use %s() on an empty %s.", + functionName, getClass().getSimpleName()); + throw new RuntimeException(msg); + } + } + + + // return the index of the minimum value + public int minIndex() { + //checkMinMax("minIndex"); + if (count == 0) return -1; + + int index = 0; + long value = values[0]; + for (int i = 1; i < count; i++) { + if (values[i] < value) { + index = i; + value = values[i]; + } + } + return index; + } + + + // return the key for the minimum value + public String minKey() { + checkMinMax("minKey"); + int index = minIndex(); + if (index == -1) { + return null; + } + return keys[index]; + } + + + // return the minimum value, or throw an error if there are no values + public long minValue() { + checkMinMax("minValue"); + return values[minIndex()]; + } + + + // return the index of the max value + public int maxIndex() { + //checkMinMax("maxIndex"); + if (count == 0) { + return -1; + } + int index = 0; + long value = values[0]; + for (int i = 1; i < count; i++) { + if (values[i] > value) { + index = i; + value = values[i]; + } + } + return index; + } + + + /** return the key corresponding to the maximum value or null if no entries */ + public String maxKey() { + //checkMinMax("maxKey"); + int index = maxIndex(); + if (index == -1) { + return null; + } + return keys[index]; + } + + + // return the maximum value or throw an error if zero length + public long maxValue() { + checkMinMax("maxIndex"); + return values[maxIndex()]; + } + + + public long sum() { + long sum = 0; + for (int i = 0; i < count; i++) { + sum += values[i]; + } + return sum; + } + + + public int index(String what) { + Integer found = indices.get(what); + return (found == null) ? -1 : found.intValue(); + } + + + protected void create(String what, long much) { + if (count == keys.length) { + keys = PApplet.expand(keys); + values = PApplet.expand(values); + } + indices.put(what, Integer.valueOf(count)); + keys[count] = what; + values[count] = much; + count++; + } + + + /** + * @webref intdict:method + * @brief Remove a key/value pair + */ + public long remove(String key) { + int index = index(key); + if (index == -1) { + throw new NoSuchElementException("'" + key + "' not found"); + } + long value = values[index]; + removeIndex(index); + return value; + } + + + public long removeIndex(int index) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + long value = values[index]; + indices.remove(keys[index]); + for (int i = index; i < count-1; i++) { + keys[i] = keys[i+1]; + values[i] = values[i+1]; + indices.put(keys[i], i); + } + count--; + keys[count] = null; + values[count] = 0; + return value; + } + + + public void swap(int a, int b) { + String tkey = keys[a]; + long tvalue = values[a]; + keys[a] = keys[b]; + values[a] = values[b]; + keys[b] = tkey; + values[b] = tvalue; + +// indices.put(keys[a], Integer.valueOf(a)); +// indices.put(keys[b], Integer.valueOf(b)); + } + + + /** + * Sort the keys alphabetically (ignoring case). Uses the value as a + * tie-breaker (only really possible with a key that has a case change). + * + * @webref intdict:method + * @brief Sort the keys alphabetically + */ + public void sortKeys() { + sortImpl(true, false, true); + } + + /** + * Sort the keys alphabetically in reverse (ignoring case). Uses the value as a + * tie-breaker (only really possible with a key that has a case change). + * + * @webref intdict:method + * @brief Sort the keys alphabetically in reverse + */ + public void sortKeysReverse() { + sortImpl(true, true, true); + } + + + /** + * Sort by values in ascending order. The smallest value will be at [0]. + * + * @webref intdict:method + * @brief Sort by values in ascending order + */ + public void sortValues() { + sortValues(true); + } + + + /** + * Set true to ensure that the order returned is identical. Slightly + * slower because the tie-breaker for identical values compares the keys. + * @param stable + */ + public void sortValues(boolean stable) { + sortImpl(false, false, stable); + } + + + /** + * Sort by values in descending order. The largest value will be at [0]. + * + * @webref intdict:method + * @brief Sort by values in descending order + */ + public void sortValuesReverse() { + sortValuesReverse(true); + } + + + public void sortValuesReverse(boolean stable) { + sortImpl(false, true, stable); + } + + + protected void sortImpl(final boolean useKeys, final boolean reverse, + final boolean stable) { + Sort s = new Sort() { + @Override + public int size() { + return count; + } + + @Override + public int compare(int a, int b) { + long diff = 0; + if (useKeys) { + diff = keys[a].compareToIgnoreCase(keys[b]); + if (diff == 0) { + diff = values[a] - values[b]; + } + } else { // sort values + diff = values[a] - values[b]; + if (diff == 0 && stable) { + diff = keys[a].compareToIgnoreCase(keys[b]); + } + } + if (diff == 0) { + return 0; + } else if (reverse) { + return diff < 0 ? 1 : -1; + } else { + return diff < 0 ? -1 : 1; + } + } + + @Override + public void swap(int a, int b) { + LongDict.this.swap(a, b); + } + }; + s.run(); + + // Set the indices after sort/swaps (performance fix 160411) + resetIndices(); + } + + + /** + * Sum all of the values in this dictionary, then return a new FloatDict of + * each key, divided by the total sum. The total for all values will be ~1.0. + * @return an IntDict with the original keys, mapped to their pct of the total + */ + public FloatDict getPercent() { + double sum = sum(); // a little more accuracy + FloatDict outgoing = new FloatDict(); + for (int i = 0; i < size(); i++) { + double percent = value(i) / sum; + outgoing.set(key(i), (float) percent); + } + return outgoing; + } + + + /** Returns a duplicate copy of this object. */ + public LongDict copy() { + LongDict outgoing = new LongDict(count); + System.arraycopy(keys, 0, outgoing.keys, 0, count); + System.arraycopy(values, 0, outgoing.values, 0, count); + for (int i = 0; i < count; i++) { + outgoing.indices.put(keys[i], i); + } + outgoing.count = count; + return outgoing; + } + + + public void print() { + for (int i = 0; i < size(); i++) { + System.out.println(keys[i] + " = " + values[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write tab-delimited entries to a PrintWriter + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(keys[i] + "\t" + values[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + StringList items = new StringList(); + for (int i = 0; i < count; i++) { + items.append(JSONObject.quote(keys[i])+ ": " + values[i]); + } + return "{ " + items.join(", ") + " }"; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); + } +} diff --git a/libs/processing-core/src/main/java/processing/data/LongList.java b/libs/processing-core/src/main/java/processing/data/LongList.java new file mode 100644 index 000000000..77bbd8b15 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/data/LongList.java @@ -0,0 +1,937 @@ +package processing.data; + +import java.io.File; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.Iterator; +import java.util.Random; + +import processing.core.PApplet; + + +// splice, slice, subset, concat, reverse + +// trim, join for String versions + + +/** + * Helper class for a list of ints. Lists are designed to have some of the + * features of ArrayLists, but to maintain the simplicity and efficiency of + * working with arrays. + * + * Functions like sort() and shuffle() always act on the list itself. To get + * a sorted copy, use list.copy().sort(). + * + * @webref data:composite + * @see FloatList + * @see StringList + */ +public class LongList implements Iterable { + protected int count; + protected long[] data; + + + public LongList() { + data = new long[10]; + } + + + /** + * @nowebref + */ + public LongList(int length) { + data = new long[length]; + } + + + /** + * @nowebref + */ + public LongList(int[] source) { + count = source.length; + data = new long[count]; + System.arraycopy(source, 0, data, 0, count); + } + + + /** + * Construct an IntList from an iterable pile of objects. + * For instance, a float array, an array of strings, who knows). + * Un-parseable or null values will be set to 0. + * @nowebref + */ + public LongList(Iterable iter) { + this(10); + for (Object o : iter) { + if (o == null) { + append(0); // missing value default + } else if (o instanceof Number) { + append(((Number) o).intValue()); + } else { + append(PApplet.parseInt(o.toString().trim())); + } + } + crop(); + } + + + /** + * Construct an IntList from a random pile of objects. + * Un-parseable or null values will be set to zero. + */ + public LongList(Object... items) { + final int missingValue = 0; // nuts, can't be last/final/second arg + + count = items.length; + data = new long[count]; + int index = 0; + for (Object o : items) { + int value = missingValue; + if (o != null) { + if (o instanceof Number) { + value = ((Number) o).intValue(); + } else { + value = PApplet.parseInt(o.toString().trim(), missingValue); + } + } + data[index++] = value; + } + } + + + static public LongList fromRange(int stop) { + return fromRange(0, stop); + } + + + static public LongList fromRange(int start, int stop) { + int count = stop - start; + LongList newbie = new LongList(count); + for (int i = 0; i < count; i++) { + newbie.set(i, start+i); + } + return newbie; + } + + + /** + * Improve efficiency by removing allocated but unused entries from the + * internal array used to store the data. Set to private, though it could + * be useful to have this public if lists are frequently making drastic + * size changes (from very large to very small). + */ + private void crop() { + if (count != data.length) { + data = PApplet.subset(data, 0, count); + } + } + + + /** + * Get the length of the list. + * + * @webref intlist:method + * @brief Get the length of the list + */ + public int size() { + return count; + } + + + public void resize(int length) { + if (length > data.length) { + long[] temp = new long[length]; + System.arraycopy(data, 0, temp, 0, count); + data = temp; + + } else if (length > count) { + Arrays.fill(data, count, length, 0); + } + count = length; + } + + + /** + * Remove all entries from the list. + * + * @webref intlist:method + * @brief Remove all entries from the list + */ + public void clear() { + count = 0; + } + + + /** + * Get an entry at a particular index. + * + * @webref intlist:method + * @brief Get an entry at a particular index + */ + public long get(int index) { + if (index >= this.count) { + throw new ArrayIndexOutOfBoundsException(index); + } + return data[index]; + } + + + /** + * Set the entry at a particular index. If the index is past the length of + * the list, it'll expand the list to accommodate, and fill the intermediate + * entries with 0s. + * + * @webref intlist:method + * @brief Set the entry at a particular index + */ + public void set(int index, int what) { + if (index >= count) { + data = PApplet.expand(data, index+1); + for (int i = count; i < index; i++) { + data[i] = 0; + } + count = index+1; + } + data[index] = what; + } + + + /** Just an alias for append(), but matches pop() */ + public void push(int value) { + append(value); + } + + + public long pop() { + if (count == 0) { + throw new RuntimeException("Can't call pop() on an empty list"); + } + long value = get(count-1); + count--; + return value; + } + + + /** + * Remove an element from the specified index + * + * @webref intlist:method + * @brief Remove an element from the specified index + */ + public long remove(int index) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + long entry = data[index]; +// int[] outgoing = new int[count - 1]; +// System.arraycopy(data, 0, outgoing, 0, index); +// count--; +// System.arraycopy(data, index + 1, outgoing, 0, count - index); +// data = outgoing; + // For most cases, this actually appears to be faster + // than arraycopy() on an array copying into itself. + for (int i = index; i < count-1; i++) { + data[i] = data[i+1]; + } + count--; + return entry; + } + + + // Remove the first instance of a particular value, + // and return the index at which it was found. + public int removeValue(int value) { + int index = index(value); + if (index != -1) { + remove(index); + return index; + } + return -1; + } + + + // Remove all instances of a particular value, + // and return the number of values found and removed + public int removeValues(int value) { + int ii = 0; + for (int i = 0; i < count; i++) { + if (data[i] != value) { + data[ii++] = data[i]; + } + } + int removed = count - ii; + count = ii; + return removed; + } + + + /** + * Add a new entry to the list. + * + * @webref intlist:method + * @brief Add a new entry to the list + */ + public void append(long value) { + if (count == data.length) { + data = PApplet.expand(data); + } + data[count++] = value; + } + + + public void append(int[] values) { + for (int v : values) { + append(v); + } + } + + + public void append(LongList list) { + for (long v : list.values()) { // will concat the list... + append(v); + } + } + + + /** Add this value, but only if it's not already in the list. */ + public void appendUnique(int value) { + if (!hasValue(value)) { + append(value); + } + } + + +// public void insert(int index, int value) { +// if (index+1 > count) { +// if (index+1 < data.length) { +// } +// } +// if (index >= data.length) { +// data = PApplet.expand(data, index+1); +// data[index] = value; +// count = index+1; +// +// } else if (count == data.length) { +// if (index >= count) { +// //int[] temp = new int[count << 1]; +// System.arraycopy(data, 0, temp, 0, index); +// temp[index] = value; +// System.arraycopy(data, index, temp, index+1, count - index); +// data = temp; +// +// } else { +// // data[] has room to grow +// // for() loop believed to be faster than System.arraycopy over itself +// for (int i = count; i > index; --i) { +// data[i] = data[i-1]; +// } +// data[index] = value; +// count++; +// } +// } + + + public void insert(int index, long value) { + insert(index, new long[] { value }); + } + + + // same as splice + public void insert(int index, long[] values) { + if (index < 0) { + throw new IllegalArgumentException("insert() index cannot be negative: it was " + index); + } + if (index >= data.length) { + throw new IllegalArgumentException("insert() index " + index + " is past the end of this list"); + } + + long[] temp = new long[count + values.length]; + + // Copy the old values, but not more than already exist + System.arraycopy(data, 0, temp, 0, Math.min(count, index)); + + // Copy the new values into the proper place + System.arraycopy(values, 0, temp, index, values.length); + +// if (index < count) { + // The index was inside count, so it's a true splice/insert + System.arraycopy(data, index, temp, index+values.length, count - index); + count = count + values.length; +// } else { +// // The index was past 'count', so the new count is weirder +// count = index + values.length; +// } + data = temp; + } + + + public void insert(int index, LongList list) { + insert(index, list.values()); + } + + + // below are aborted attempts at more optimized versions of the code + // that are harder to read and debug... + +// if (index + values.length >= count) { +// // We're past the current 'count', check to see if we're still allocated +// // index 9, data.length = 10, values.length = 1 +// if (index + values.length < data.length) { +// // There's still room for these entries, even though it's past 'count'. +// // First clear out the entries leading up to it, however. +// for (int i = count; i < index; i++) { +// data[i] = 0; +// } +// data[index] = +// } +// if (index >= data.length) { +// int length = index + values.length; +// int[] temp = new int[length]; +// System.arraycopy(data, 0, temp, 0, count); +// System.arraycopy(values, 0, temp, index, values.length); +// data = temp; +// count = data.length; +// } else { +// +// } +// +// } else if (count == data.length) { +// int[] temp = new int[count << 1]; +// System.arraycopy(data, 0, temp, 0, index); +// temp[index] = value; +// System.arraycopy(data, index, temp, index+1, count - index); +// data = temp; +// +// } else { +// // data[] has room to grow +// // for() loop believed to be faster than System.arraycopy over itself +// for (int i = count; i > index; --i) { +// data[i] = data[i-1]; +// } +// data[index] = value; +// count++; +// } + + + /** Return the first index of a particular value. */ + public int index(int what) { + /* + if (indexCache != null) { + try { + return indexCache.get(what); + } catch (Exception e) { // not there + return -1; + } + } + */ + for (int i = 0; i < count; i++) { + if (data[i] == what) { + return i; + } + } + return -1; + } + + + // !!! TODO this is not yet correct, because it's not being reset when + // the rest of the entries are changed +// protected void cacheIndices() { +// indexCache = new HashMap(); +// for (int i = 0; i < count; i++) { +// indexCache.put(data[i], i); +// } +// } + + /** + * @webref intlist:method + * @brief Check if a number is a part of the list + */ + public boolean hasValue(int value) { +// if (indexCache == null) { +// cacheIndices(); +// } +// return index(what) != -1; + for (int i = 0; i < count; i++) { + if (data[i] == value) { + return true; + } + } + return false; + } + + /** + * @webref intlist:method + * @brief Add one to a value + */ + public void increment(int index) { + if (count <= index) { + resize(index + 1); + } + data[index]++; + } + + + private void boundsProblem(int index, String method) { + final String msg = String.format("The list size is %d. " + + "You cannot %s() to element %d.", count, method, index); + throw new ArrayIndexOutOfBoundsException(msg); + } + + + /** + * @webref intlist:method + * @brief Add to a value + */ + public void add(int index, int amount) { + if (index < count) { + data[index] += amount; + } else { + boundsProblem(index, "add"); + } + } + + /** + * @webref intlist:method + * @brief Subtract from a value + */ + public void sub(int index, int amount) { + if (index < count) { + data[index] -= amount; + } else { + boundsProblem(index, "sub"); + } + } + + /** + * @webref intlist:method + * @brief Multiply a value + */ + public void mult(int index, int amount) { + if (index < count) { + data[index] *= amount; + } else { + boundsProblem(index, "mult"); + } + } + + /** + * @webref intlist:method + * @brief Divide a value + */ + public void div(int index, int amount) { + if (index < count) { + data[index] /= amount; + } else { + boundsProblem(index, "div"); + } + } + + + private void checkMinMax(String functionName) { + if (count == 0) { + String msg = + String.format("Cannot use %s() on an empty %s.", + functionName, getClass().getSimpleName()); + throw new RuntimeException(msg); + } + } + + + /** + * @webref intlist:method + * @brief Return the smallest value + */ + public long min() { + checkMinMax("min"); + long outgoing = data[0]; + for (int i = 1; i < count; i++) { + if (data[i] < outgoing) outgoing = data[i]; + } + return outgoing; + } + + + // returns the index of the minimum value. + // if there are ties, it returns the first one found. + public int minIndex() { + checkMinMax("minIndex"); + long value = data[0]; + int index = 0; + for (int i = 1; i < count; i++) { + if (data[i] < value) { + value = data[i]; + index = i; + } + } + return index; + } + + + /** + * @webref intlist:method + * @brief Return the largest value + */ + public long max() { + checkMinMax("max"); + long outgoing = data[0]; + for (int i = 1; i < count; i++) { + if (data[i] > outgoing) outgoing = data[i]; + } + return outgoing; + } + + + // returns the index of the maximum value. + // if there are ties, it returns the first one found. + public int maxIndex() { + checkMinMax("maxIndex"); + long value = data[0]; + int index = 0; + for (int i = 1; i < count; i++) { + if (data[i] > value) { + value = data[i]; + index = i; + } + } + return index; + } + + + public int sum() { + long amount = sumLong(); + if (amount > Integer.MAX_VALUE) { + throw new RuntimeException("sum() exceeds " + Integer.MAX_VALUE + ", use sumLong()"); + } + if (amount < Integer.MIN_VALUE) { + throw new RuntimeException("sum() less than " + Integer.MIN_VALUE + ", use sumLong()"); + } + return (int) amount; + } + + + public long sumLong() { + long sum = 0; + for (int i = 0; i < count; i++) { + sum += data[i]; + } + return sum; + } + + + /** + * Sorts the array in place. + * + * @webref intlist:method + * @brief Sorts the array, lowest to highest + */ + public void sort() { + Arrays.sort(data, 0, count); + } + + + /** + * Reverse sort, orders values from highest to lowest. + * + * @webref intlist:method + * @brief Reverse sort, orders values from highest to lowest + */ + public void sortReverse() { + new Sort() { + @Override + public int size() { + return count; + } + + @Override + public int compare(int a, int b) { + long diff = data[b] - data[a]; + return diff == 0 ? 0 : (diff < 0 ? -1 : 1); + } + + @Override + public void swap(int a, int b) { + long temp = data[a]; + data[a] = data[b]; + data[b] = temp; + } + }.run(); + } + + + // use insert() +// public void splice(int index, int value) { +// } + + +// public void subset(int start) { +// subset(start, count - start); +// } +// +// +// public void subset(int start, int num) { +// for (int i = 0; i < num; i++) { +// data[i] = data[i+start]; +// } +// count = num; +// } + + /** + * @webref intlist:method + * @brief Reverse the order of the list elements + */ + public void reverse() { + int ii = count - 1; + for (int i = 0; i < count/2; i++) { + long t = data[i]; + data[i] = data[ii]; + data[ii] = t; + --ii; + } + } + + + /** + * Randomize the order of the list elements. Note that this does not + * obey the randomSeed() function in PApplet. + * + * @webref intlist:method + * @brief Randomize the order of the list elements + */ + public void shuffle() { + Random r = new Random(); + int num = count; + while (num > 1) { + int value = r.nextInt(num); + num--; + long temp = data[num]; + data[num] = data[value]; + data[value] = temp; + } + } + + + /** + * Randomize the list order using the random() function from the specified + * sketch, allowing shuffle() to use its current randomSeed() setting. + */ + public void shuffle(PApplet sketch) { + int num = count; + while (num > 1) { + int value = (int) sketch.random(num); + num--; + long temp = data[num]; + data[num] = data[value]; + data[value] = temp; + } + } + + + public LongList copy() { + LongList outgoing = new LongList(data); + outgoing.count = count; + return outgoing; + } + + + /** + * Returns the actual array being used to store the data. For advanced users, + * this is the fastest way to access a large list. Suitable for iterating + * with a for() loop, but modifying the list will have terrible consequences. + */ + public long[] values() { + crop(); + return data; + } + + + @Override + public Iterator iterator() { +// public Iterator valueIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + LongList.this.remove(index); + index--; + } + + public Long next() { + return data[++index]; + } + + public boolean hasNext() { + return index+1 < count; + } + }; + } + + + /** + * Create a new array with a copy of all the values. + * + * @return an array sized by the length of the list with each of the values. + * @webref intlist:method + * @brief Create a new array with a copy of all the values + */ + public int[] array() { + return array(null); + } + + + /** + * Copy values into the specified array. If the specified array is null or + * not the same size, a new array will be allocated. + * @param array + */ + public int[] array(int[] array) { + if (array == null || array.length != count) { + array = new int[count]; + } + System.arraycopy(data, 0, array, 0, count); + return array; + } + + +// public int[] toIntArray() { +// int[] outgoing = new int[count]; +// for (int i = 0; i < count; i++) { +// outgoing[i] = (int) data[i]; +// } +// return outgoing; +// } + + +// public long[] toLongArray() { +// long[] outgoing = new long[count]; +// for (int i = 0; i < count; i++) { +// outgoing[i] = (long) data[i]; +// } +// return outgoing; +// } + + +// public float[] toFloatArray() { +// float[] outgoing = new float[count]; +// System.arraycopy(data, 0, outgoing, 0, count); +// return outgoing; +// } + + +// public double[] toDoubleArray() { +// double[] outgoing = new double[count]; +// for (int i = 0; i < count; i++) { +// outgoing[i] = data[i]; +// } +// return outgoing; +// } + + +// public String[] toStringArray() { +// String[] outgoing = new String[count]; +// for (int i = 0; i < count; i++) { +// outgoing[i] = String.valueOf(data[i]); +// } +// return outgoing; +// } + + + /** + * Returns a normalized version of this array. Called getPercent() for + * consistency with the Dict classes. It's a getter method because it needs + * to returns a new list (because IntList/Dict can't do percentages or + * normalization in place on int values). + */ + public FloatList getPercent() { + double sum = 0; + for (float value : array()) { + sum += value; + } + FloatList outgoing = new FloatList(count); + for (int i = 0; i < count; i++) { + double percent = data[i] / sum; + outgoing.set(i, (float) percent); + } + return outgoing; + } + + +// /** +// * Count the number of times each entry is found in this list. +// * Converts each entry to a String so it can be used as a key. +// */ +// public IntDict getTally() { +// IntDict outgoing = new IntDict(); +// for (int i = 0; i < count; i++) { +// outgoing.increment(String.valueOf(data[i])); +// } +// return outgoing; +// } + + + public LongList getSubset(int start) { + return getSubset(start, count - start); + } + + + public LongList getSubset(int start, int num) { + int[] subset = new int[num]; + System.arraycopy(data, start, subset, 0, num); + return new LongList(subset); + } + + + public String join(String separator) { + if (count == 0) { + return ""; + } + StringBuilder sb = new StringBuilder(); + sb.append(data[0]); + for (int i = 1; i < count; i++) { + sb.append(separator); + sb.append(data[i]); + } + return sb.toString(); + } + + + public void print() { + for (int i = 0; i < count; i++) { + System.out.format("[%d] %d%n", i, data[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write entries to a PrintWriter, one per line + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(data[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + return "[ " + join(", ") + " ]"; + } + + + @Override + public String toString() { + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); + } +} diff --git a/core/src/processing/data/Sort.java b/libs/processing-core/src/main/java/processing/data/Sort.java similarity index 83% rename from core/src/processing/data/Sort.java rename to libs/processing-core/src/main/java/processing/data/Sort.java index d205edb12..a83fea551 100644 --- a/core/src/processing/data/Sort.java +++ b/libs/processing-core/src/main/java/processing/data/Sort.java @@ -31,8 +31,8 @@ protected void sort(int i, int j) { protected int partition(int left, int right) { int pivot = right; do { - while (compare(++left, pivot) < 0) ; - while ((right != 0) && (compare(--right, pivot) > 0)) ; + while (compare(++left, pivot) < 0) { } + while ((right != 0) && (compare(--right, pivot) > 0)) { } swap(left, right); } while (left < right); swap(left, right); @@ -41,6 +41,6 @@ protected int partition(int left, int right) { abstract public int size(); - abstract public float compare(int a, int b); + abstract public int compare(int a, int b); abstract public void swap(int a, int b); } \ No newline at end of file diff --git a/core/src/processing/data/StringDict.java b/libs/processing-core/src/main/java/processing/data/StringDict.java similarity index 57% rename from core/src/processing/data/StringDict.java rename to libs/processing-core/src/main/java/processing/data/StringDict.java index 6f896dfac..c66a61e4d 100644 --- a/core/src/processing/data/StringDict.java +++ b/libs/processing-core/src/main/java/processing/data/StringDict.java @@ -3,6 +3,7 @@ import java.io.*; import java.util.HashMap; import java.util.Iterator; +import java.util.NoSuchElementException; import processing.core.PApplet; @@ -23,7 +24,7 @@ public class StringDict { protected String[] values; /** Internal implementation for faster lookups */ - private HashMap indices = new HashMap(); + private HashMap indices = new HashMap<>(); public StringDict() { @@ -63,11 +64,13 @@ public StringDict(BufferedReader reader) { if (pieces.length == 2) { keys[count] = pieces[0]; values[count] = pieces[1]; + indices.put(keys[count], count); count++; } } } + /** * @nowebref */ @@ -83,6 +86,49 @@ public StringDict(String[] keys, String[] values) { } } + + /** + * Constructor to allow (more intuitive) inline initialization, e.g.: + *
    +   * new StringDict(new String[][] {
    +   *   { "key1", "value1" },
    +   *   { "key2", "value2" }
    +   * });
    +   * 
    + * It's no Python, but beats a static { } block with HashMap.put() statements. + */ + public StringDict(String[][] pairs) { + count = pairs.length; + this.keys = new String[count]; + this.values = new String[count]; + for (int i = 0; i < count; i++) { + keys[i] = pairs[i][0]; + values[i] = pairs[i][1]; + indices.put(keys[i], i); + } + } + + + /** + * Create a dictionary that maps between column titles and cell entries + * in a TableRow. If two columns have the same name, the later column's + * values will override the earlier values. + */ + public StringDict(TableRow row) { + this(row.getColumnCount()); + + String[] titles = row.getColumnTitles(); + if (titles == null) { + titles = new StringList(IntList.fromRange(row.getColumnCount())).array(); + } + for (int col = 0; col < row.getColumnCount(); col++) { + set(titles[col], row.getString(col)); + } + // remove unused and overwritten entries + crop(); + } + + /** * @webref stringdict:method * @brief Returns the number of key/value pairs @@ -92,6 +138,29 @@ public int size() { } + /** + * Resize the internal data, this can only be used to shrink the list. + * Helpful for situations like sorting and then grabbing the top 50 entries. + */ + public void resize(int length) { + if (length > count) { + throw new IllegalArgumentException("resize() can only be used to shrink the dictionary"); + } + if (length < 1) { + throw new IllegalArgumentException("resize(" + length + ") is too small, use 1 or higher"); + } + + String[] newKeys = new String[length]; + String[] newValues = new String[length]; + PApplet.arrayCopy(keys, newKeys, length); + PApplet.arrayCopy(values, newValues, length); + keys = newKeys; + values = newValues; + count = length; + resetIndices(); + } + + /** * Remove all entries. * @@ -100,10 +169,67 @@ public int size() { */ public void clear() { count = 0; - indices = new HashMap(); + indices = new HashMap<>(); + } + + + private void resetIndices() { + indices = new HashMap<>(count); + for (int i = 0; i < count; i++) { + indices.put(keys[i], i); + } + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public class Entry { + public String key; + public String value; + + Entry(String key, String value) { + this.key = key; + this.value = value; + } + } + + + public Iterable entries() { + return new Iterable() { + + public Iterator iterator() { + return entryIterator(); + } + }; } + public Iterator entryIterator() { + return new Iterator() { + int index = -1; + + public void remove() { + removeIndex(index); + index--; + } + + public Entry next() { + ++index; + Entry e = new Entry(keys[index], values[index]); + return e; + } + + public boolean hasNext() { + return index+1 < size(); + } + }; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + public String key(int index) { return keys[index]; } @@ -117,38 +243,33 @@ protected void crop() { } -// /** -// * Return the internal array being used to store the keys. Allocated but -// * unused entries will be removed. This array should not be modified. -// */ -// public String[] keys() { -// crop(); -// return keys; -// } - - /** - * @webref stringdict:method - * @brief Return the internal array being used to store the keys - */ public Iterable keys() { return new Iterable() { + @Override public Iterator iterator() { - return new Iterator() { - int index = -1; + return keyIterator(); + } + }; + } - public void remove() { - removeIndex(index); - } - public String next() { - return key(++index); - } + // Use this to iterate when you want to be able to remove elements along the way + public Iterator keyIterator() { + return new Iterator() { + int index = -1; - public boolean hasNext() { - return index+1 < size(); - } - }; + public void remove() { + removeIndex(index); + index--; + } + + public String next() { + return key(++index); + } + + public boolean hasNext() { + return index+1 < size(); } }; } @@ -161,6 +282,7 @@ public boolean hasNext() { * @brief Return a copy of the internal keys array */ public String[] keyArray() { + crop(); return keyArray(null); } @@ -185,22 +307,29 @@ public String value(int index) { public Iterable values() { return new Iterable() { + @Override public Iterator iterator() { - return new Iterator() { - int index = -1; + return valueIterator(); + } + }; + } - public void remove() { - removeIndex(index); - } - public String next() { - return value(++index); - } + public Iterator valueIterator() { + return new Iterator() { + int index = -1; - public boolean hasNext() { - return index+1 < size(); - } - }; + public void remove() { + removeIndex(index); + index--; + } + + public String next() { + return value(++index); + } + + public boolean hasNext() { + return index+1 < size(); } }; } @@ -213,6 +342,7 @@ public boolean hasNext() { * @brief Create a new array and copy each of the values into it */ public String[] valueArray() { + crop(); return valueArray(null); } @@ -243,25 +373,43 @@ public String get(String key) { return values[index]; } + + public String get(String key, String alternate) { + int index = index(key); + if (index == -1) return alternate; + return values[index]; + } + + /** * @webref stringdict:method * @brief Create a new key/value pair or change the value of one */ - public void set(String key, String amount) { + public void set(String key, String value) { int index = index(key); if (index == -1) { - create(key, amount); + create(key, value); } else { - values[index] = amount; + values[index] = value; } } + public void setIndex(int index, String key, String value) { + if (index < 0 || index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } + keys[index] = key; + values[index] = value; + } + + public int index(String what) { Integer found = indices.get(what); return (found == null) ? -1 : found.intValue(); } + /** * @webref stringdict:method * @brief Check if a key is a part of the data structure @@ -276,7 +424,7 @@ protected void create(String key, String value) { keys = PApplet.expand(keys); values = PApplet.expand(values); } - indices.put(key, new Integer(count)); + indices.put(key, Integer.valueOf(count)); keys[count] = key; values[count] = value; count++; @@ -286,12 +434,14 @@ protected void create(String key, String value) { * @webref stringdict:method * @brief Remove a key/value pair */ - public int remove(String key) { + public String remove(String key) { int index = index(key); - if (index != -1) { - removeIndex(index); + if (index == -1) { + throw new NoSuchElementException("'" + key + "' not found"); } - return index; + String value = values[index]; + removeIndex(index); + return value; } @@ -299,9 +449,8 @@ public String removeIndex(int index) { if (index < 0 || index >= count) { throw new ArrayIndexOutOfBoundsException(index); } - //System.out.println("index is " + which + " and " + keys[which]); - String key = keys[index]; - indices.remove(key); + String value = values[index]; + indices.remove(keys[index]); for (int i = index; i < count-1; i++) { keys[i] = keys[i+1]; values[i] = values[i+1]; @@ -310,11 +459,12 @@ public String removeIndex(int index) { count--; keys[count] = null; values[count] = null; - return key; + return value; } - protected void swap(int a, int b) { + + public void swap(int a, int b) { String tkey = keys[a]; String tvalue = values[a]; keys[a] = keys[b]; @@ -322,8 +472,8 @@ protected void swap(int a, int b) { keys[b] = tkey; values[b] = tvalue; - indices.put(keys[a], new Integer(a)); - indices.put(keys[b], new Integer(b)); +// indices.put(keys[a], Integer.valueOf(a)); +// indices.put(keys[b], Integer.valueOf(b)); } @@ -340,7 +490,7 @@ public void sortKeys() { /** * @webref stringdict:method - * @brief Sort the keys alphabetially in reverse + * @brief Sort the keys alphabetically in reverse */ public void sortKeysReverse() { sortImpl(true, true); @@ -375,7 +525,7 @@ public int size() { } @Override - public float compare(int a, int b) { + public int compare(int a, int b) { int diff = 0; if (useKeys) { diff = keys[a].compareToIgnoreCase(keys[b]); @@ -397,6 +547,9 @@ public void swap(int a, int b) { } }; s.run(); + + // Set the indices after sort/swaps (performance fix 160411) + resetIndices(); } @@ -413,9 +566,25 @@ public StringDict copy() { } + public void print() { + for (int i = 0; i < size(); i++) { + System.out.println(keys[i] + " = " + values[i]); + } + } + + /** - * Write tab-delimited entries out to - * @param writer + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write tab-delimited entries to a PrintWriter */ public void write(PrintWriter writer) { for (int i = 0; i < count; i++) { @@ -425,17 +594,20 @@ public void write(PrintWriter writer) { } + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + StringList items = new StringList(); + for (int i = 0; i < count; i++) { + items.append(JSONObject.quote(keys[i])+ ": " + JSONObject.quote(values[i])); + } + return "{ " + items.join(", ") + " }"; + } + + @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName() + " size=" + size() + " { "); - for (int i = 0; i < size(); i++) { - if (i != 0) { - sb.append(", "); - } - sb.append("\"" + keys[i] + "\": \"" + values[i] + "\""); - } - sb.append(" }"); - return sb.toString(); + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } } diff --git a/core/src/processing/data/StringList.java b/libs/processing-core/src/main/java/processing/data/StringList.java similarity index 85% rename from core/src/processing/data/StringList.java rename to libs/processing-core/src/main/java/processing/data/StringList.java index 5533747a3..c4de6d33c 100644 --- a/core/src/processing/data/StringList.java +++ b/libs/processing-core/src/main/java/processing/data/StringList.java @@ -1,5 +1,7 @@ package processing.data; +import java.io.File; +import java.io.PrintWriter; import java.util.Arrays; import java.util.Iterator; import java.util.Random; @@ -44,6 +46,27 @@ public StringList(String[] list) { } + /** + * Construct a StringList from a random pile of objects. Null values will + * stay null, but all the others will be converted to String values. + */ + public StringList(Object... items) { + count = items.length; + data = new String[count]; + int index = 0; + for (Object o : items) { +// // Not gonna go with null values staying that way because perhaps +// // the most common case here is to immediately call join() or similar. +// data[index++] = String.valueOf(o); + // Keep null values null (because join() will make non-null anyway) + if (o != null) { // leave null values null + data[index] = o.toString(); + } + index++; + } + } + + /** * Create from something iterable, for instance: * StringList list = new StringList(hashMap.keySet()); @@ -113,6 +136,9 @@ public void clear() { * @brief Get an entry at a particular index */ public String get(int index) { + if (index >= count) { + throw new ArrayIndexOutOfBoundsException(index); + } return data[index]; } @@ -137,6 +163,22 @@ public void set(int index, String what) { } + /** Just an alias for append(), but matches pop() */ + public void push(String value) { + append(value); + } + + + public String pop() { + if (count == 0) { + throw new RuntimeException("Can't call pop() on an empty list"); + } + String value = get(count-1); + data[--count] = null; // avoid leak + return value; + } + + /** * Remove an element from the specified index. * @@ -274,6 +316,14 @@ public void append(StringList list) { } + /** Add this value, but only if it's not already in the list. */ + public void appendUnique(String value) { + if (!hasValue(value)) { + append(value); + } + } + + // public void insert(int index, int value) { // if (index+1 > count) { // if (index+1 < data.length) { @@ -304,12 +354,17 @@ public void append(StringList list) { // } + public void insert(int index, String value) { + insert(index, new String[] { value }); + } + + // same as splice public void insert(int index, String[] values) { if (index < 0) { throw new IllegalArgumentException("insert() index cannot be negative: it was " + index); } - if (index >= values.length) { + if (index >= data.length) { throw new IllegalArgumentException("insert() index " + index + " is past the end of this list"); } @@ -461,8 +516,8 @@ public int size() { } @Override - public float compare(int a, int b) { - float diff = data[a].compareToIgnoreCase(data[b]); + public int compare(int a, int b) { + int diff = data[a].compareToIgnoreCase(data[b]); return reverse ? -diff : diff; } @@ -495,7 +550,7 @@ public void swap(int a, int b) { /** * @webref stringlist:method - * @brief To come... + * @brief Reverse the order of the list elements */ public void reverse() { int ii = count - 1; @@ -592,6 +647,7 @@ public String[] values() { } + @Override public Iterator iterator() { // return valueIterator(); // } @@ -603,6 +659,7 @@ public Iterator iterator() { public void remove() { StringList.this.remove(index); + index--; } public String next() { @@ -629,7 +686,8 @@ public String[] array() { /** - * Copy as many values as possible into the specified array. + * Copy values into the specified array. If the specified array is null or + * not the same size, a new array will be allocated. * @param array */ public String[] array(String[] array) { @@ -679,14 +737,6 @@ public IntDict getOrder() { } -// public void println() { -// for (int i = 0; i < count; i++) { -// System.out.println("[" + i + "] " + data[i]); -// } -// System.out.flush(); -// } - - public String join(String separator) { if (count == 0) { return ""; @@ -701,23 +751,48 @@ public String join(String separator) { } -// static public StringList split(String value, char delim) { -// String[] array = PApplet.split(value, delim); -// return new StringList(array); -// } + public void print() { + for (int i = 0; i < count; i++) { + System.out.format("[%d] %s%n", i, data[i]); + } + } + + + /** + * Save tab-delimited entries to a file (TSV format, UTF-8 encoding) + */ + public void save(File file) { + PrintWriter writer = PApplet.createWriter(file); + write(writer); + writer.close(); + } + + + /** + * Write entries to a PrintWriter, one per line + */ + public void write(PrintWriter writer) { + for (int i = 0; i < count; i++) { + writer.println(data[i]); + } + writer.flush(); + } + + + /** + * Return this dictionary as a String in JSON format. + */ + public String toJSON() { + StringList temp = new StringList(); + for (String item : this) { + temp.append(JSONObject.quote(item)); + } + return "[ " + temp.join(", ") + " ]"; + } @Override public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(getClass().getSimpleName() + " size=" + size() + " [ "); - for (int i = 0; i < size(); i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(i + ": \"" + data[i] + "\""); - } - sb.append(" ]"); - return sb.toString(); + return getClass().getSimpleName() + " size=" + size() + " " + toJSON(); } -} \ No newline at end of file +} diff --git a/core/src/processing/data/Table.java b/libs/processing-core/src/main/java/processing/data/Table.java similarity index 80% rename from core/src/processing/data/Table.java rename to libs/processing-core/src/main/java/processing/data/Table.java index 2a3c4f857..2048376cf 100644 --- a/core/src/processing/data/Table.java +++ b/libs/processing-core/src/main/java/processing/data/Table.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2011-12 Ben Fry and Casey Reas Copyright (c) 2006-11 Ben Fry This library is free software; you can redistribute it and/or @@ -25,6 +26,7 @@ import java.io.*; import java.lang.reflect.*; +import java.nio.charset.Charset; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -34,6 +36,7 @@ import java.util.concurrent.Executors; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; import javax.xml.parsers.ParserConfigurationException; @@ -60,6 +63,7 @@ */ public class Table { protected int rowCount; + protected int allocCount; // protected boolean skipEmptyRows = true; // protected boolean skipCommentLines = true; @@ -153,12 +157,40 @@ public Table(InputStream input, String options) throws IOException { public Table(Iterable rows) { - boolean typed = false; - for (TableRow row : rows) { - if (!typed) { - setColumnTypes(row.getColumnTypes()); + init(); + + int row = 0; + int alloc = 10; + + for (TableRow incoming : rows) { + if (row == 0) { + setColumnTypes(incoming.getColumnTypes()); + setColumnTitles(incoming.getColumnTitles()); + // Do this after setting types, otherwise it'll attempt to parse the + // allocated but empty rows, and drive CATEGORY columns nutso. + setRowCount(alloc); + // sometimes more columns than titles (and types?) + setColumnCount(incoming.getColumnCount()); + + } else if (row == alloc) { + // Far more efficient than re-allocating all columns and doing a copy + alloc *= 2; + setRowCount(alloc); } - addRow(row); + + //addRow(row); +// try { + setRow(row++, incoming); +// } catch (ArrayIndexOutOfBoundsException aioobe) { +// for (int i = 0; i < incoming.getColumnCount(); i++) { +// System.out.format("[%d] %s%n", i, incoming.getString(i)); +// } +// throw aioobe; +// } + } + // Shrink the table to only the rows that were used + if (row != alloc) { + setRowCount(row); } } @@ -272,7 +304,7 @@ protected String checkOptions(File file, String options) throws IOException { static final String[] loadExtensions = { "csv", "tsv", "ods", "bin" }; - static final String[] saveExtensions = { "csv", "tsv", "html", "bin" }; + static final String[] saveExtensions = { "csv", "tsv", "ods", "bin", "html" }; static public String extensionOptions(boolean loading, String filename, String options) { String extension = PApplet.checkExtension(filename); @@ -294,19 +326,18 @@ static public String extensionOptions(boolean loading, String filename, String o protected void parse(InputStream input, String options) throws IOException { - //init(); - - boolean awfulCSV = false; +// boolean awfulCSV = false; boolean header = false; String extension = null; boolean binary = false; + String encoding = "UTF-8"; String worksheet = null; final String sheetParam = "worksheet="; String[] opts = null; if (options != null) { - opts = PApplet.splitTokens(options, " ,"); + opts = PApplet.trim(PApplet.split(options, ',')); for (String opt : opts) { if (opt.equals("tsv")) { extension = "tsv"; @@ -315,8 +346,9 @@ protected void parse(InputStream input, String options) throws IOException { } else if (opt.equals("ods")) { extension = "ods"; } else if (opt.equals("newlines")) { - awfulCSV = true; - extension = "csv"; + //awfulCSV = true; + //extension = "csv"; + throw new IllegalArgumentException("The 'newlines' option is no longer necessary."); } else if (opt.equals("bin")) { binary = true; extension = "bin"; @@ -326,6 +358,8 @@ protected void parse(InputStream input, String options) throws IOException { worksheet = opt.substring(sheetParam.length()); } else if (opt.startsWith("dictionary=")) { // ignore option, this is only handled by PApplet + } else if (opt.startsWith("encoding=")) { + encoding = opt.substring(9); } else { throw new IllegalArgumentException("'" + opt + "' is not a valid option for loading a Table"); } @@ -340,17 +374,30 @@ protected void parse(InputStream input, String options) throws IOException { loadBinary(input); } else if (extension.equals("ods")) { - odsParse(input, worksheet); + odsParse(input, worksheet, header); } else { - BufferedReader reader = PApplet.createReader(input); - if (awfulCSV) { + InputStreamReader isr = new InputStreamReader(input, encoding); + BufferedReader reader = new BufferedReader(isr); + + // strip out the Unicode BOM, if present + reader.mark(1); + int c = reader.read(); + // if not the BOM, back up to the beginning again + if (c != '\uFEFF') { + reader.reset(); + } + + /* + if (awfulCSV) { parseAwfulCSV(reader, header); } else if ("tsv".equals(extension)) { parseBasic(reader, header, true); } else if ("csv".equals(extension)) { parseBasic(reader, header, false); } + */ + parseBasic(reader, header, "tsv".equals(extension)); } } @@ -369,16 +416,16 @@ protected void parseBasic(BufferedReader reader, setRowCount(row << 1); } if (row == 0 && header) { - setColumnTitles(tsv ? PApplet.split(line, '\t') : splitLineCSV(line)); + setColumnTitles(tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader)); header = false; } else { - setRow(row, tsv ? PApplet.split(line, '\t') : splitLineCSV(line)); + setRow(row, tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader)); row++; } - // this is problematic unless we're going to calculate rowCount first if (row % 10000 == 0) { /* + // this is problematic unless we're going to calculate rowCount first if (row < rowCount) { int pct = (100 * row) / rowCount; if (pct != prev) { // also prevents "0%" from showing up @@ -410,11 +457,16 @@ protected void parseBasic(BufferedReader reader, // } + /* protected void parseAwfulCSV(BufferedReader reader, boolean header) throws IOException { char[] c = new char[100]; int count = 0; boolean insideQuote = false; + + int alloc = 100; + setRowCount(100); + int row = 0; int col = 0; int ch; @@ -460,14 +512,23 @@ protected void parseAwfulCSV(BufferedReader reader, } setString(row, col, new String(c, 0, count)); count = 0; - if (row == 0 && header) { + row++; + if (row == 1 && header) { // Use internal row removal (efficient because only one row). removeTitleRow(); // Un-set the header variable so that next time around, we don't // just get stuck into a loop, removing the 0th row repeatedly. header = false; + // Reset the number of rows (removeTitleRow() won't reset our local 'row' counter) + row = 0; + } +// if (row % 1000 == 0) { +// PApplet.println(PApplet.nfc(row)); +// } + if (row == alloc) { + alloc *= 2; + setRowCount(alloc); } - row++; col = 0; } else if (ch == ',') { @@ -489,73 +550,276 @@ protected void parseAwfulCSV(BufferedReader reader, if (count > 0) { setString(row, col, new String(c, 0, count)); } + row++; // set row to row count (the current row index + 1) + if (alloc != row) { + setRowCount(row); // shrink to the actual size + } } + */ - /** - * Parse a line of text as comma-separated values, returning each value as - * one entry in an array of String objects. Remove quotes from entries that - * begin and end with them, and convert 'escaped' quotes to actual quotes. - * @param line line of text to be parsed - * @return an array of the individual values formerly separated by commas - */ - static protected String[] splitLineCSV(String line) { - char[] c = line.toCharArray(); - int rough = 1; // at least one - boolean quote = false; - for (int i = 0; i < c.length; i++) { - if (!quote && (c[i] == ',')) { - rough++; - } else if (c[i] == '\"') { - quote = !quote; + static class CommaSeparatedLine { + char[] c; + String[] pieces; + int pieceCount; + +// int offset; + int start; //, stop; + + String[] handle(String line, BufferedReader reader) throws IOException { +// PApplet.println("handle() called for: " + line); + start = 0; + pieceCount = 0; + c = line.toCharArray(); + + // get tally of number of columns and allocate the array + int cols = 1; // the first comma indicates the second column + boolean quote = false; + for (int i = 0; i < c.length; i++) { + if (!quote && (c[i] == ',')) { + cols++; + } else if (c[i] == '\"') { + // double double quotes (escaped quotes like "") will simply toggle + // this back and forth, so it should remain accurate + quote = !quote; + } + } + pieces = new String[cols]; + +// while (offset < c.length) { +// start = offset; + while (start < c.length) { + boolean enough = ingest(); + while (!enough) { + // found a newline inside the quote, grab another line + String nextLine = reader.readLine(); +// System.out.println("extending to " + nextLine); + if (nextLine == null) { +// System.err.println(line); + throw new IOException("Found a quoted line that wasn't terminated properly."); + } + // for simplicity, not bothering to skip what's already been read + // from c (and reset the offset to 0), opting to make a bigger array + // with both lines. + char[] temp = new char[c.length + 1 + nextLine.length()]; + PApplet.arrayCopy(c, temp, c.length); + // NOTE: we're converting to \n here, which isn't perfect + temp[c.length] = '\n'; + nextLine.getChars(0, nextLine.length(), temp, c.length + 1); +// c = temp; + return handle(new String(temp), reader); + //System.out.println(" full line is now " + new String(c)); + //stop = nextComma(c, offset); + //System.out.println("stop is now " + stop); + //enough = ingest(); + } + } + + // Make any remaining entries blanks instead of nulls. Empty columns from + // CSV are always "" not null, so this handles successive commas in a line + for (int i = pieceCount; i < pieces.length; i++) { + pieces[i] = ""; + } +// PApplet.printArray(pieces); + return pieces; + } + + protected void addPiece(int start, int stop, boolean quotes) { + if (quotes) { + int dest = start; + for (int i = start; i < stop; i++) { + if (c[i] == '\"') { + ++i; // step over the quote + } + if (i != dest) { + c[dest] = c[i]; + } + dest++; + } + pieces[pieceCount++] = new String(c, start, dest - start); + + } else { + pieces[pieceCount++] = new String(c, start, stop - start); } } - String[] pieces = new String[rough]; - int pieceCount = 0; - int offset = 0; - while (offset < c.length) { - int start = offset; - int stop = nextComma(c, offset); - offset = stop + 1; // next time around, need to step over the comment - if (c[start] == '\"' && c[stop-1] == '\"') { - start++; - stop--; + + /** + * Returns the next comma (not inside a quote) in the specified array. + * @param c array to search + * @param index offset at which to start looking + * @return index of the comma, or -1 if line ended inside an unclosed quote + */ + protected boolean ingest() { + boolean hasEscapedQuotes = false; + // not possible +// if (index == c.length) { // we're already at the end +// return c.length; +// } + boolean quoted = c[start] == '\"'; + if (quoted) { + start++; // step over the quote } int i = start; - int ii = start; - while (i < stop) { + while (i < c.length) { +// PApplet.println(c[i] + " i=" + i); if (c[i] == '\"') { - i++; // skip over pairs of double quotes become one - } - if (i != ii) { - c[ii] = c[i]; + // if this fella started with a quote + if (quoted) { + if (i == c.length-1) { + // closing quote for field; last field on the line + addPiece(start, i, hasEscapedQuotes); + start = c.length; + return true; + + } else if (c[i+1] == '\"') { + // an escaped quote inside a quoted field, step over it + hasEscapedQuotes = true; + i += 2; + + } else if (c[i+1] == ',') { + // that was our closing quote, get outta here + addPiece(start, i, hasEscapedQuotes); + start = i+2; + return true; + + } else { + // This is a lone-wolf quote, occasionally seen in exports. + // It's a single quote in the middle of some other text, + // and not escaped properly. Pray for the best! + i++; + } + + } else { // not a quoted line + if (i == c.length-1) { + // we're at the end of the line, can't have an unescaped quote + throw new RuntimeException("Unterminated quote at end of line"); + + } else if (c[i+1] == '\"') { + // step over this crummy quote escape + hasEscapedQuotes = true; + i += 2; + + } else { + throw new RuntimeException("Unterminated quoted field mid-line"); + } + } + } else if (!quoted && c[i] == ',') { + addPiece(start, i, hasEscapedQuotes); + start = i+1; + return true; + + } else if (!quoted && i == c.length-1) { + addPiece(start, c.length, hasEscapedQuotes); + start = c.length; + return true; + + } else { // nothing all that interesting + i++; } - i++; - ii++; } - String s = new String(c, start, ii - start); - pieces[pieceCount++] = s; +// if (!quote && (c[i] == ',')) { +// // found a comma, return this location +// return i; +// } else if (c[i] == '\"') { +// // if it's a quote, then either the next char is another quote, +// // or if this is a quoted entry, it better be a comma +// quote = !quote; +// } +// } + + // if still inside a quote, indicate that another line should be read + if (quoted) { + return false; + } + +// // made it to the end of the array with no new comma +// return c.length; + + throw new RuntimeException("not sure how..."); } - // make any remaining entries blanks instead of nulls - for (int i = pieceCount; i < pieces.length; i++) { - pieces[i] = ""; + } + + CommaSeparatedLine csl; + + /** + * Parse a line of text as comma-separated values, returning each value as + * one entry in an array of String objects. Remove quotes from entries that + * begin and end with them, and convert 'escaped' quotes to actual quotes. + * @param line line of text to be parsed + * @return an array of the individual values formerly separated by commas + */ + protected String[] splitLineCSV(String line, BufferedReader reader) throws IOException { + if (csl == null) { + csl = new CommaSeparatedLine(); } - return pieces; + return csl.handle(line, reader); } + /** + * Returns the next comma (not inside a quote) in the specified array. + * @param c array to search + * @param index offset at which to start looking + * @return index of the comma, or -1 if line ended inside an unclosed quote + */ + /* static protected int nextComma(char[] c, int index) { - boolean quote = false; + if (index == c.length) { // we're already at the end + return c.length; + } + boolean quoted = c[index] == '\"'; + if (quoted) { + index++; // step over the quote + } for (int i = index; i < c.length; i++) { + if (c[i] == '\"') { + // if this fella started with a quote + if (quoted) { + if (i == c.length-1) { + //return -1; // ran out of chars + // closing quote for field; last field on the line + return c.length; + } else if (c[i+1] == '\"') { + // an escaped quote inside a quoted field, step over it + i++; + } else if (c[i+1] == ',') { + // that's our closing quote, get outta here + return i+1; + } + + } else { // not a quoted line + if (i == c.length-1) { + // we're at the end of the line, can't have an unescaped quote + //return -1; // ran out of chars + throw new RuntimeException("Unterminated quoted field at end of line"); + } else if (c[i+1] == '\"') { + // step over this crummy quote escape + ++i; + } else { + throw new RuntimeException("Unterminated quoted field mid-line"); + } + } + } else if (!quoted && c[i] == ',') { + return i; + } if (!quote && (c[i] == ',')) { + // found a comma, return this location return i; } else if (c[i] == '\"') { + // if it's a quote, then either the next char is another quote, + // or if this is a quoted entry, it better be a comma quote = !quote; } } + // if still inside a quote, indicate that another line should be read + if (quote) { + return -1; + } + // made it to the end of the array with no new comma return c.length; } + */ /** @@ -578,7 +842,7 @@ private InputStream odsFindContentXML(InputStream input) { } - protected void odsParse(InputStream input, String worksheet) { + protected void odsParse(InputStream input, String worksheet, boolean header) { try { InputStream contentStream = odsFindContentXML(input); XML xml = new XML(contentStream); @@ -594,7 +858,7 @@ protected void odsParse(InputStream input, String worksheet) { for (XML sheet : sheets) { // System.out.println(sheet.getAttribute("table:name")); if (worksheet == null || worksheet.equals(sheet.getString("table:name"))) { - odsParseSheet(sheet); + odsParseSheet(sheet, header); found = true; if (worksheet == null) { break; // only read the first sheet @@ -625,7 +889,7 @@ protected void odsParse(InputStream input, String worksheet) { * Parses a single sheet of XML from this file. * @param The XML object for a single worksheet from the ODS file */ - private void odsParseSheet(XML sheet) { + private void odsParseSheet(XML sheet, boolean header) { // Extra

    or tags inside the text tag for the cell will be stripped. // Different from showing formulas, and not quite the same as 'save as // displayed' option when saving from inside OpenOffice. Only time we @@ -674,7 +938,7 @@ private void odsParseSheet(XML sheet) { cellData = textpContent; // nothing fancy, the text is in the text:p element } else { XML[] textpKids = textp.getChildren(); - StringBuffer cellBuffer = new StringBuffer(); + StringBuilder cellBuffer = new StringBuilder(); for (XML kid : textpKids) { String kidName = kid.getName(); if (kidName == null) { @@ -722,18 +986,24 @@ private void odsParseSheet(XML sheet) { } } } - if (rowNotNull && rowRepeat > 1) { - String[] rowStrings = getStringRow(rowIndex); - for (int r = 1; r < rowRepeat; r++) { - addRow(rowStrings); + if (header) { + removeTitleRow(); // efficient enough on the first row + header = false; // avoid infinite loop + + } else { + if (rowNotNull && rowRepeat > 1) { + String[] rowStrings = getStringRow(rowIndex); + for (int r = 1; r < rowRepeat; r++) { + addRow(rowStrings); + } } + rowIndex += rowRepeat; } - rowIndex += rowRepeat; } } - private void odsAppendNotNull(XML kid, StringBuffer buffer) { + private void odsAppendNotNull(XML kid, StringBuilder buffer) { String content = kid.getContent(); if (content != null) { buffer.append(content); @@ -807,7 +1077,7 @@ public void parseInto(Object enclosingObject, String fieldName) { } Field[] fields = target.getDeclaredFields(); - ArrayList inuse = new ArrayList(); + ArrayList inuse = new ArrayList<>(); for (Field field : fields) { String name = field.getName(); if (getColumnIndex(name, false) != -1) { @@ -915,7 +1185,7 @@ public boolean save(OutputStream output, String options) { throw new IllegalArgumentException("No extension specified for saving this Table"); } - String[] opts = PApplet.splitTokens(options, ", "); + String[] opts = PApplet.trim(PApplet.split(options, ',')); // Only option for save is the extension, so we can safely grab the last extension = opts[opts.length - 1]; boolean found = false; @@ -934,6 +1204,13 @@ public boolean save(OutputStream output, String options) { writeCSV(writer); } else if (extension.equals("tsv")) { writeTSV(writer); + } else if (extension.equals("ods")) { + try { + saveODS(output); + } catch (IOException e) { + e.printStackTrace(); + return false; + } } else if (extension.equals("html")) { writeHTML(writer); } else if (extension.equals("bin")) { @@ -982,12 +1259,18 @@ protected void writeTSV(PrintWriter writer) { protected void writeCSV(PrintWriter writer) { if (columnTitles != null) { - for (int col = 0; col < columns.length; col++) { + for (int col = 0; col < getColumnCount(); col++) { if (col != 0) { writer.print(','); } - if (columnTitles[col] != null) { - writeEntryCSV(writer, columnTitles[col]); + try { + if (columnTitles[col] != null) { // col < columnTitles.length && + writeEntryCSV(writer, columnTitles[col]); + } + } catch (ArrayIndexOutOfBoundsException e) { + PApplet.printArray(columnTitles); + PApplet.printArray(columns); + throw e; } } writer.println(); @@ -1050,28 +1333,51 @@ protected void writeEntryCSV(PrintWriter writer, String entry) { protected void writeHTML(PrintWriter writer) { - writer.println(""); + writer.println(""); +// writer.println(""); +// writer.println(""); + writer.println(""); writer.println(""); writer.println(" "); writer.println(""); writer.println(""); writer.println(" "); + + if (hasColumnTitles()) { + writer.println(" "); + for (String entry : getColumnTitles()) { + writer.print(" "); + } + writer.println(" "); + } + for (int row = 0; row < getRowCount(); row++) { writer.println(" "); for (int col = 0; col < getColumnCount(); col++) { String entry = getString(row, col); writer.print(" "); + if (entry != null) { + // probably not a great idea to mess w/ the export +// if (entry.startsWith("<") && entry.endsWith(">")) { +// writer.print(entry); +// } else { + writeEntryHTML(writer, entry); +// } + } + writer.println(""); } writer.println(" "); } writer.println("
    "); + if (entry != null) { + writeEntryHTML(writer, entry); + } + writer.println("
    "); - writeEntryHTML(writer, entry); - writer.println("
    "); writer.println(""); - writer.println(""); + writer.println(""); writer.flush(); } @@ -1085,16 +1391,15 @@ protected void writeEntryHTML(PrintWriter writer, String entry) { writer.print(">"); } else if (c == '&') { writer.print("&"); - } else if (c == '\'') { - writer.print("'"); +// } else if (c == '\'') { // only in XML +// writer.print("'"); } else if (c == '"') { writer.print("""); - // not necessary with UTF-8? -// } else if (c < 32 || c > 127) { -// writer.print("&#"); -// writer.print((int) c); -// writer.print(';'); + } else if (c < 32 || c > 127) { // keep in ASCII or Tidy complains + writer.print("&#"); + writer.print((int) c); + writer.print(';'); } else { writer.print(c); @@ -1103,6 +1408,207 @@ protected void writeEntryHTML(PrintWriter writer, String entry) { } + protected void saveODS(OutputStream os) throws IOException { + ZipOutputStream zos = new ZipOutputStream(os); + + final String xmlHeader = ""; + + ZipEntry entry = new ZipEntry("META-INF/manifest.xml"); + String[] lines = new String[] { + xmlHeader, + "", + " ", + " ", + " ", + " ", + " ", + "" + }; + zos.putNextEntry(entry); + zos.write(PApplet.join(lines, "\n").getBytes()); + zos.closeEntry(); + + /* + entry = new ZipEntry("meta.xml"); + lines = new String[] { + xmlHeader, + "" + }; + zos.putNextEntry(entry); + zos.write(PApplet.join(lines, "\n").getBytes()); + zos.closeEntry(); + + entry = new ZipEntry("meta.xml"); + lines = new String[] { + xmlHeader, + "" + }; + zos.putNextEntry(entry); + zos.write(PApplet.join(lines, "\n").getBytes()); + zos.closeEntry(); + + entry = new ZipEntry("settings.xml"); + lines = new String[] { + xmlHeader, + "" + }; + zos.putNextEntry(entry); + zos.write(PApplet.join(lines, "\n").getBytes()); + zos.closeEntry(); + + entry = new ZipEntry("styles.xml"); + lines = new String[] { + xmlHeader, + "" + }; + zos.putNextEntry(entry); + zos.write(PApplet.join(lines, "\n").getBytes()); + zos.closeEntry(); + */ + + final String[] dummyFiles = new String[] { + "meta.xml", "settings.xml", "styles.xml" + }; + lines = new String[] { + xmlHeader, + "" + }; + byte[] dummyBytes = PApplet.join(lines, "\n").getBytes(); + for (String filename : dummyFiles) { + entry = new ZipEntry(filename); + zos.putNextEntry(entry); + zos.write(dummyBytes); + zos.closeEntry(); + } + + // + + entry = new ZipEntry("mimetype"); + zos.putNextEntry(entry); + zos.write("application/vnd.oasis.opendocument.spreadsheet".getBytes()); + zos.closeEntry(); + + // + + entry = new ZipEntry("content.xml"); + zos.putNextEntry(entry); + //lines = new String[] { + writeUTF(zos, new String[] { + xmlHeader, + "", + " ", + " ", + " " + }); + //zos.write(PApplet.join(lines, "\n").getBytes()); + + byte[] rowStart = " \n".getBytes(); + byte[] rowStop = " \n".getBytes(); + + if (hasColumnTitles()) { + zos.write(rowStart); + for (int i = 0; i < getColumnCount(); i++) { + saveStringODS(zos, columnTitles[i]); + } + zos.write(rowStop); + } + + for (TableRow row : rows()) { + zos.write(rowStart); + for (int i = 0; i < getColumnCount(); i++) { + if (columnTypes[i] == STRING || columnTypes[i] == CATEGORY) { + saveStringODS(zos, row.getString(i)); + } else { + saveNumberODS(zos, row.getString(i)); + } + } + zos.write(rowStop); + } + + //lines = new String[] { + writeUTF(zos, new String[] { + " ", + " ", + " ", + "" + }); + //zos.write(PApplet.join(lines, "\n").getBytes()); + zos.closeEntry(); + + zos.flush(); + zos.close(); + } + + + void saveStringODS(OutputStream output, String text) throws IOException { + // At this point, I should have just used the XML library. But this does + // save us from having to create the entire document in memory again before + // writing to the file. So while it's dorky, the outcome is still useful. + StringBuilder sanitized = new StringBuilder(); + if (text != null) { + char[] array = text.toCharArray(); + for (char c : array) { + if (c == '&') { + sanitized.append("&"); + } else if (c == '\'') { + sanitized.append("'"); + } else if (c == '"') { + sanitized.append("""); + } else if (c == '<') { + sanitized.append("<"); + } else if (c == '>') { + sanitized.append("&rt;"); + } else if (c < 32 || c > 127) { + sanitized.append("&#" + ((int) c) + ";"); + } else { + sanitized.append(c); + } + } + } + + writeUTF(output, + " ", + " " + sanitized + "", + " "); + } + + + void saveNumberODS(OutputStream output, String text) throws IOException { + writeUTF(output, + " ", + " " + text + "", + " "); + } + + + static Charset utf8; + + static void writeUTF(OutputStream output, String... lines) throws IOException { + if (utf8 == null) { + utf8 = Charset.forName("UTF-8"); + } + for (String str : lines) { + output.write(str.getBytes(utf8)); + output.write('\n'); + } + } + + protected void saveBinary(OutputStream os) throws IOException { DataOutputStream output = new DataOutputStream(new BufferedOutputStream(os)); output.writeInt(0x9007AB1E); // version @@ -1163,7 +1669,12 @@ protected void saveBinary(OutputStream os) throws IOException { output.writeDouble(row.getDouble(col)); break; case CATEGORY: - output.writeInt(columnCategories[col].index(row.getString(col))); + String peace = row.getString(col); + if (peace.equals(missingString)) { + output.writeInt(missingCategory); + } else { + output.writeInt(columnCategories[col].index(peace)); + } break; } } @@ -1295,7 +1806,7 @@ public void addColumn(String title) { /** - * @param type the type to be used for the new column: INT, LONG, FLOAT, DOUBLE, STRING, or CATEGORY + * @param type the type to be used for the new column: INT, LONG, FLOAT, DOUBLE, or STRING */ public void addColumn(String title, int type) { insertColumn(columns.length, title, type); @@ -1394,6 +1905,7 @@ public void removeColumn(int column) { } } + /** * @webref table:method * @brief Gets the number of columns in a table @@ -1434,14 +1946,10 @@ public void setColumnType(String columnName, String columnType) { } - /** - * Set the data type for a column so that using it is more efficient. - * @param column the column to change - * @param columnType One of int, long, float, double, or String. - */ - public void setColumnType(int column, String columnType) { + static int parseColumnType(String columnType) { + columnType = columnType.toLowerCase(); int type = -1; - if (columnType.equals("String")) { + if (columnType.equals("string")) { type = STRING; } else if (columnType.equals("int")) { type = INT; @@ -1456,7 +1964,17 @@ public void setColumnType(int column, String columnType) { } else { throw new IllegalArgumentException("'" + columnType + "' is not a valid column type."); } - setColumnType(column, type); + return type; + } + + + /** + * Set the data type for a column so that using it is more efficient. + * @param column the column to change + * @param columnType One of int, long, float, double, string, or category. + */ + public void setColumnType(int column, String columnType) { + setColumnType(column, parseColumnType(columnType)); } @@ -1477,7 +1995,7 @@ public void setColumnType(int column, int newType) { int[] intData = new int[rowCount]; for (int row = 0; row < rowCount; row++) { String s = getString(row, column); - intData[row] = PApplet.parseInt(s, missingInt); + intData[row] = (s == null) ? missingInt : PApplet.parseInt(s, missingInt); } columns[column] = intData; break; @@ -1487,7 +2005,7 @@ public void setColumnType(int column, int newType) { for (int row = 0; row < rowCount; row++) { String s = getString(row, column); try { - longData[row] = Long.parseLong(s); + longData[row] = (s == null) ? missingLong : Long.parseLong(s); } catch (NumberFormatException nfe) { longData[row] = missingLong; } @@ -1499,7 +2017,7 @@ public void setColumnType(int column, int newType) { float[] floatData = new float[rowCount]; for (int row = 0; row < rowCount; row++) { String s = getString(row, column); - floatData[row] = PApplet.parseFloat(s, missingFloat); + floatData[row] = (s == null) ? missingFloat : PApplet.parseFloat(s, missingFloat); } columns[column] = floatData; break; @@ -1509,7 +2027,7 @@ public void setColumnType(int column, int newType) { for (int row = 0; row < rowCount; row++) { String s = getString(row, column); try { - doubleData[row] = Double.parseDouble(s); + doubleData[row] = (s == null) ? missingDouble : Double.parseDouble(s); } catch (NumberFormatException nfe) { doubleData[row] = missingDouble; } @@ -1558,6 +2076,7 @@ public void setTableType(String type) { public void setColumnTypes(int[] types) { + ensureColumn(types.length - 1); for (int col = 0; col < types.length; col++) { setColumnType(col, types[col]); } @@ -1573,6 +2092,7 @@ public void setColumnTypes(int[] types) { * @param dictionary */ public void setColumnTypes(final Table dictionary) { + ensureColumn(dictionary.getRowCount() - 1); int titleCol = 0; int typeCol = 1; if (dictionary.hasColumnTitles()) { @@ -1696,7 +2216,7 @@ protected int getColumnIndex(String name, boolean report) { // only create this on first get(). subsequent calls to set the title will // also update this array, but only if it exists. if (columnIndices == null) { - columnIndices = new HashMap(); + columnIndices = new HashMap<>(); for (int col = 0; col < columns.length; col++) { columnIndices.put(columnTitles[col], col); } @@ -1809,13 +2329,16 @@ public TableRow addRow() { * @param source a reference to the original row to be duplicated */ public TableRow addRow(TableRow source) { - int row = rowCount; + return setRow(rowCount, source); + } + + + public TableRow setRow(int row, TableRow source) { // Make sure there are enough columns to add this data ensureBounds(row, source.getColumnCount() - 1); - for (int col = 0; col < columns.length; col++) { + for (int col = 0; col < Math.min(source.getColumnCount(), columns.length); col++) { switch (columnTypes[col]) { - case CATEGORY: case INT: setInt(row, col, source.getInt(col)); break; @@ -1831,6 +2354,14 @@ public TableRow addRow(TableRow source) { case STRING: setString(row, col, source.getString(col)); break; + case CATEGORY: + int index = source.getInt(col); + setInt(row, col, index); + if (!columnCategories[col].hasCategory(index)) { + columnCategories[col].setCategory(index, source.getString(col)); + } + break; + default: throw new RuntimeException("no types"); } @@ -1848,6 +2379,15 @@ public TableRow addRow(Object[] columnData) { } + public void addRows(Table source) { + int index = getRowCount(); + setRowCount(index + source.getRowCount()); + for (TableRow row : source.rows()) { + setRow(index++, row); + } + } + + public void insertRow(int insert, Object[] columnData) { for (int col = 0; col < columns.length; col++) { switch (columnTypes[col]) { @@ -1855,44 +2395,47 @@ public void insertRow(int insert, Object[] columnData) { case INT: { int[] intTemp = new int[rowCount+1]; System.arraycopy(columns[col], 0, intTemp, 0, insert); - System.arraycopy(columns[col], insert, intTemp, insert+1, (rowCount - insert) + 1); + System.arraycopy(columns[col], insert, intTemp, insert+1, rowCount - insert); columns[col] = intTemp; break; } case LONG: { long[] longTemp = new long[rowCount+1]; System.arraycopy(columns[col], 0, longTemp, 0, insert); - System.arraycopy(columns[col], insert, longTemp, insert+1, (rowCount - insert) + 1); + System.arraycopy(columns[col], insert, longTemp, insert+1, rowCount - insert); columns[col] = longTemp; break; } case FLOAT: { float[] floatTemp = new float[rowCount+1]; System.arraycopy(columns[col], 0, floatTemp, 0, insert); - System.arraycopy(columns[col], insert, floatTemp, insert+1, (rowCount - insert) + 1); + System.arraycopy(columns[col], insert, floatTemp, insert+1, rowCount - insert); columns[col] = floatTemp; break; } case DOUBLE: { double[] doubleTemp = new double[rowCount+1]; System.arraycopy(columns[col], 0, doubleTemp, 0, insert); - System.arraycopy(columns[col], insert, doubleTemp, insert+1, (rowCount - insert) + 1); + System.arraycopy(columns[col], insert, doubleTemp, insert+1, rowCount - insert); columns[col] = doubleTemp; break; } case STRING: { String[] stringTemp = new String[rowCount+1]; System.arraycopy(columns[col], 0, stringTemp, 0, insert); - System.arraycopy(columns[col], insert, stringTemp, insert+1, (rowCount - insert) + 1); + System.arraycopy(columns[col], insert, stringTemp, insert+1, rowCount - insert); columns[col] = stringTemp; break; } } } + // Need to increment before setRow(), because it calls ensureBounds() + // https://github.com/processing/processing/issues/5406 + ++rowCount; setRow(insert, columnData); - rowCount++; } + /** * @webref table:method * @brief Removes a row from a table @@ -2083,7 +2626,12 @@ protected void setRowCol(int row, int col, Object piece) { if (piece == null) { indexData[row] = missingCategory; } else { - indexData[row] = columnCategories[col].index(String.valueOf(piece)); + String peace = String.valueOf(piece); + if (peace.equals(missingString)) { // missingString might be null + indexData[row] = missingCategory; + } else { + indexData[row] = columnCategories[col].index(peace); + } } break; default: @@ -2258,6 +2806,27 @@ public int getColumnType(int column) { public int[] getColumnTypes() { return table.getColumnTypes(); } + + public String getColumnTitle(int column) { + return table.getColumnTitle(column); + } + + public String[] getColumnTitles() { + return table.getColumnTitles(); + } + + public void print() { + write(new PrintWriter(System.out)); + } + + public void write(PrintWriter writer) { + for (int i = 0 ; i < getColumnCount(); i++) { + if (i != 0) { + writer.print('\t'); + } + writer.print(getString(i)); + } + } } @@ -2916,11 +3485,19 @@ public String getString(int row, int column) { return missingString; } return columnCategories[column].key(cat); - } else { - return String.valueOf(Array.get(columns[column], row)); + } else if (columnTypes[column] == FLOAT) { + if (Float.isNaN(getFloat(row, column))) { + return null; + } + } else if (columnTypes[column] == DOUBLE) { + if (Double.isNaN(getFloat(row, column))) { + return null; + } } + return String.valueOf(Array.get(columns[column], row)); } + /** * @param columnName title of the column to reference */ @@ -2929,6 +3506,9 @@ public String getString(int row, String columnName) { } + /** + * Treat entries with this string as "missing". Also used for categorial. + */ public void setMissingString(String value) { missingString = value; } @@ -3221,7 +3801,7 @@ public int matchRowIndex(String what, String columnName) { /** * Return a list of rows that contain the String passed in. If there are no * matches, a zero length array will be returned (not a null array). - * @param what the String to match + * @param regexp the String to match * @param column ID number of the column to search */ public int[] matchRowIndices(String regexp, int column) { @@ -3355,9 +3935,18 @@ public void replace(String orig, String replacement) { public void replace(String orig, String replacement, int col) { if (columnTypes[col] == STRING) { String[] stringData = (String[]) columns[col]; - for (int row = 0; row < rowCount; row++) { - if (stringData[row].equals(orig)) { - stringData[row] = replacement; + + if (orig != null) { + for (int row = 0; row < rowCount; row++) { + if (orig.equals(stringData[row])) { + stringData[row] = replacement; + } + } + } else { // null is a special case (and faster anyway) + for (int row = 0; row < rowCount; row++) { + if (stringData[row] == null) { + stringData[row] = replacement; + } } } } @@ -3372,9 +3961,9 @@ public void replace(String orig, String replacement, String colName) { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - public void replaceAll(String orig, String replacement) { + public void replaceAll(String regex, String replacement) { for (int col = 0; col < columns.length; col++) { - replaceAll(orig, replacement, col); + replaceAll(regex, replacement, col); } } @@ -3397,7 +3986,7 @@ public void replaceAll(String regex, String replacement, int column) { /** * Run String.replaceAll() on all entries in a column. * Only works with columns that are already String values. - * @param what the String to match + * @param regex the String to match * @param columnName title of the column to search */ public void replaceAll(String regex, String replacement, String columnName) { @@ -3463,16 +4052,77 @@ public void removeTokens(String tokens, String columnName) { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + /** * @webref table:method * @brief Trims whitespace from values * @see Table#removeTokens(String) */ public void trim() { + columnTitles = PApplet.trim(columnTitles); for (int col = 0; col < getColumnCount(); col++) { trim(col); } + // remove empty columns + int lastColumn = getColumnCount() - 1; + //while (isEmptyColumn(lastColumn) && lastColumn >= 0) { + while (isEmptyArray(getStringColumn(lastColumn)) && lastColumn >= 0) { + lastColumn--; + } + setColumnCount(lastColumn + 1); + + // trim() works from both sides + while (getColumnCount() > 0 && isEmptyArray(getStringColumn(0))) { + removeColumn(0); + } + + // remove empty rows (starting from the end) + int lastRow = lastRowIndex(); + //while (isEmptyRow(lastRow) && lastRow >= 0) { + while (isEmptyArray(getStringRow(lastRow)) && lastRow >= 0) { + lastRow--; + } + setRowCount(lastRow + 1); + + while (getRowCount() > 0 && isEmptyArray(getStringRow(0))) { + removeRow(0); + } + } + + + protected boolean isEmptyArray(String[] contents) { + for (String entry : contents) { + if (entry != null && entry.length() > 0) { + return false; + } + } + return true; + } + + + /* + protected boolean isEmptyColumn(int column) { + String[] contents = getStringColumn(column); + for (String entry : contents) { + if (entry != null && entry.length() > 0) { + return false; + } + } + return true; + } + + + protected boolean isEmptyRow(int row) { + String[] contents = getStringRow(row); + for (String entry : contents) { + if (entry != null && entry.length() > 0) { + return false; + } + } + return true; } + */ + /** * @param column ID number of the column to trim @@ -3548,9 +4198,9 @@ protected void checkBounds(int row, int column) { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - class HashMapBlows { - HashMap dataToIndex = new HashMap(); - ArrayList indexToData = new ArrayList(); + static class HashMapBlows { + HashMap dataToIndex = new HashMap<>(); + ArrayList indexToData = new ArrayList<>(); HashMapBlows() { } @@ -3558,6 +4208,7 @@ class HashMapBlows { read(input); } + /** gets the index, and creates one if it doesn't already exist. */ int index(String key) { Integer value = dataToIndex.get(key); if (value != null) { @@ -3574,6 +4225,18 @@ String key(int index) { return indexToData.get(index); } + boolean hasCategory(int index) { + return index < size() && indexToData.get(index) != null; + } + + void setCategory(int index, String name) { + while (indexToData.size() <= index) { + indexToData.add(null); + } + indexToData.set(index, name); + dataToIndex.put(name, index); + } + int size() { return dataToIndex.size(); } @@ -3595,9 +4258,11 @@ private void writeln(PrintWriter writer) throws IOException { void read(DataInputStream input) throws IOException { int count = input.readInt(); - dataToIndex = new HashMap(count); + //System.out.println("found " + count + " entries in category map"); + dataToIndex = new HashMap<>(count); for (int i = 0; i < count; i++) { String str = input.readUTF(); + //System.out.println(i + " " + str); dataToIndex.put(str, i); indexToData.add(str); } @@ -3629,12 +4294,21 @@ void read(DataInputStream input) throws IOException { // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - + /** + * Sorts (orders) a table based on the values in a column. + * + * @webref table:method + * @brief Orders a table based on the values in a column + * @param columnName the name of the column to sort + * @see Table#trim() + */ public void sort(String columnName) { sort(getColumnIndex(columnName), false); } - + /** + * @param column the column ID, e.g. 0, 1, 2 + */ public void sort(int column) { sort(column, false); } @@ -3660,7 +4334,7 @@ public int size() { } @Override - public float compare(int index1, int index2) { + public int compare(int index1, int index2) { int a = reverse ? order[index2] : order[index1]; int b = reverse ? order[index1] : order[index2]; @@ -3668,13 +4342,24 @@ public float compare(int index1, int index2) { case INT: return getInt(a, column) - getInt(b, column); case LONG: - return getLong(a, column) - getLong(b, column); + long diffl = getLong(a, column) - getLong(b, column); + return diffl == 0 ? 0 : (diffl < 0 ? -1 : 1); case FLOAT: - return getFloat(a, column) - getFloat(b, column); + float difff = getFloat(a, column) - getFloat(b, column); + return difff == 0 ? 0 : (difff < 0 ? -1 : 1); case DOUBLE: - return (float) (getDouble(a, column) - getDouble(b, column)); + double diffd = getDouble(a, column) - getDouble(b, column); + return diffd == 0 ? 0 : (diffd < 0 ? -1 : 1); case STRING: - return getString(a, column).compareToIgnoreCase(getString(b, column)); + String string1 = getString(a, column); + if (string1 == null) { + string1 = ""; // avoid NPE when cells are left empty + } + String string2 = getString(b, column); + if (string2 == null) { + string2 = ""; + } + return string1.compareToIgnoreCase(string2); case CATEGORY: return getInt(a, column) - getInt(b, column); default: @@ -3833,13 +4518,13 @@ public FloatDict getFloatDict(String keyColumnName, String valueColumnName) { public FloatDict getFloatDict(int keyColumn, int valueColumn) { return new FloatDict(getStringColumn(keyColumn), - getFloatColumn(valueColumn)); + getFloatColumn(valueColumn)); } public StringDict getStringDict(String keyColumnName, String valueColumnName) { return new StringDict(getStringColumn(keyColumnName), - getStringColumn(valueColumnName)); + getStringColumn(valueColumnName)); } @@ -3849,6 +4534,39 @@ public StringDict getStringDict(int keyColumn, int valueColumn) { } + public Map getRowMap(String columnName) { + int col = getColumnIndex(columnName); + return (col == -1) ? null : getRowMap(col); + } + + + /** + * Return a mapping that connects the entry from a column back to the row + * from which it came. For instance: + *

    +   * Table t = loadTable("country-data.tsv", "header");
    +   * // use the contents of the 'country' column to index the table
    +   * Map lookup = t.getRowMap("country");
    +   * // get the row that has "us" in the "country" column:
    +   * TableRow usRow = lookup.get("us");
    +   * // get an entry from the 'population' column
    +   * int population = usRow.getInt("population");
    +   * 
    + */ + public Map getRowMap(int column) { + Map outgoing = new HashMap<>(); + for (int row = 0; row < getRowCount(); row++) { + String id = getString(row, column); + outgoing.put(id, new RowPointer(this, row)); + } +// for (TableRow row : rows()) { +// String id = row.getString(column); +// outgoing.put(id, row); +// } + return outgoing; + } + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . @@ -4045,7 +4763,7 @@ protected void convertBasic(BufferedReader reader, boolean tsv, int prev = -1; int row = 0; while ((line = reader.readLine()) != null) { - convertRow(output, tsv ? PApplet.split(line, '\t') : splitLineCSV(line)); + convertRow(output, tsv ? PApplet.split(line, '\t') : splitLineCSV(line, reader)); row++; if (row % 10000 == 0) { @@ -4127,7 +4845,12 @@ protected void convertRow(DataOutputStream output, String[] pieces) throws IOExc } break; case CATEGORY: - output.writeInt(columnCategories[col].index(pieces[col])); + String peace = pieces[col]; + if (peace.equals(missingString)) { + output.writeInt(missingCategory); + } else { + output.writeInt(columnCategories[col].index(peace)); + } break; } } @@ -4193,4 +4916,20 @@ private void convertRowCol(DataOutputStream output, int row, int col, String pie } } */ + + + /** Make a copy of the current table */ + public Table copy() { + return new Table(rows()); + } + + + public void write(PrintWriter writer) { + writeTSV(writer); + } + + + public void print() { + writeTSV(new PrintWriter(System.out)); + } } diff --git a/core/src/processing/data/TableRow.java b/libs/processing-core/src/main/java/processing/data/TableRow.java similarity index 61% rename from core/src/processing/data/TableRow.java rename to libs/processing-core/src/main/java/processing/data/TableRow.java index 7107ee693..3ac59fe4c 100644 --- a/core/src/processing/data/TableRow.java +++ b/libs/processing-core/src/main/java/processing/data/TableRow.java @@ -1,5 +1,7 @@ package processing.data; +import java.io.PrintWriter; + /** * @webref data:composite * @see Table @@ -19,6 +21,7 @@ public interface TableRow { * @see TableRow#getFloat(int) */ public String getString(int column); + /** * @param columnName title of the column to reference */ @@ -32,12 +35,24 @@ public interface TableRow { * @see TableRow#getString(int) */ public int getInt(int column); + /** * @param columnName title of the column to reference */ public int getInt(String columnName); + /** + * @brief Get a long value from the specified column + * @param column ID number of the column to reference + * @see TableRow#getFloat(int) + * @see TableRow#getString(int) + */ + public long getLong(int column); + + /** + * @param columnName title of the column to reference + */ public long getLong(String columnName); /** @@ -48,12 +63,23 @@ public interface TableRow { * @see TableRow#getString(int) */ public float getFloat(int column); + /** * @param columnName title of the column to reference */ public float getFloat(String columnName); - + + /** + * @brief Get a double value from the specified column + * @param column ID number of the column to reference + * @see TableRow#getInt(int) + * @see TableRow#getString(int) + */ public double getDouble(int column); + + /** + * @param columnName title of the column to reference + */ public double getDouble(String columnName); /** @@ -79,12 +105,24 @@ public interface TableRow { * @see TableRow#setString(int, String) */ public void setInt(int column, int value); + /** * @param columnName title of the target column */ public void setInt(String columnName, int value); - + + /** + * @brief Store a long value in the specified column + * @param column ID number of the target column + * @param value value to assign + * @see TableRow#setFloat(int, float) + * @see TableRow#setString(int, String) + */ public void setLong(int column, long value); + + /** + * @param columnName title of the target column + */ public void setLong(String columnName, long value); /** @@ -96,17 +134,65 @@ public interface TableRow { * @see TableRow#setString(int, String) */ public void setFloat(int column, float value); + /** * @param columnName title of the target column */ public void setFloat(String columnName, float value); + /** + * @brief Store a double value in the specified column + * @param column ID number of the target column + * @param value value to assign + * @see TableRow#setFloat(int, float) + * @see TableRow#setString(int, String) + */ public void setDouble(int column, double value); + + /** + * @param columnName title of the target column + */ public void setDouble(String columnName, double value); + /** + * @webref tablerow:method + * @brief Get the column count. + * @return count of all columns + */ public int getColumnCount(); + + /** + * @brief Get the column type. + * @param columnName title of the target column + * @return type of the column + */ public int getColumnType(String columnName); + + /** + * @param column ID number of the target column + */ public int getColumnType(int column); - + + /** + * @brief Get the all column types + * @return list of all column types + */ public int[] getColumnTypes(); + + /** + * @webref tablerow:method + * @brief Get the column title. + * @param column ID number of the target column + * @return title of the column + */ + public String getColumnTitle(int column); + + /** + * @brief Get the all column titles + * @return list of all column titles + */ + public String[] getColumnTitles(); + + public void write(PrintWriter writer); + public void print(); } diff --git a/core/src/processing/data/XML.java b/libs/processing-core/src/main/java/processing/data/XML.java similarity index 86% rename from core/src/processing/data/XML.java rename to libs/processing-core/src/main/java/processing/data/XML.java index 28023b768..f2c2e32cc 100644 --- a/core/src/processing/data/XML.java +++ b/libs/processing-core/src/main/java/processing/data/XML.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2009-12 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or @@ -33,6 +33,9 @@ import javax.xml.transform.*; import javax.xml.transform.dom.*; import javax.xml.transform.stream.*; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathFactory; import processing.core.PApplet; @@ -42,7 +45,6 @@ * representing a single node of an XML tree. * * @webref data:composite - * @see PApplet#createXML(String) * @see PApplet#loadXML(String) * @see PApplet#parseXML(String) * @see PApplet#saveXML(XML, String) @@ -82,7 +84,9 @@ protected XML() { } /** - * Advanced users only; see loadXML() in PApplet. + * Advanced users only; use loadXML() in PApplet. This is not a supported + * function and is subject to change. It is available simply for users that + * would like to handle the exceptions in a particular way. * * @nowebref */ @@ -92,7 +96,7 @@ public XML(File file) throws IOException, ParserConfigurationException, SAXExcep /** - * Advanced users only; see loadXML() in PApplet. + * Advanced users only; use loadXML() in PApplet. * * @nowebref */ @@ -109,19 +113,31 @@ public XML(InputStream input) throws IOException, ParserConfigurationException, /** - * Shouldn't be part of main p5 reference, this is for advanced users. - * Note that while it doesn't accept anything but UTF-8, this is preserved - * so that we have some chance of implementing that in the future. + * Unlike the loadXML() method in PApplet, this version works with files + * that are not in UTF-8 format. * * @nowebref */ public XML(InputStream input, String options) throws IOException, ParserConfigurationException, SAXException { - this(PApplet.createReader(input), options); + //this(PApplet.createReader(input), options); // won't handle non-UTF8 + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + + try { + // Prevent 503 errors from www.w3.org + factory.setAttribute("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + } catch (IllegalArgumentException e) { + // ignore this; Android doesn't like it + } + + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new InputSource(input)); + node = document.getDocumentElement(); } /** - * Advanced users only; see loadXML() in PApplet. + * Advanced users only; use loadXML() in PApplet. * * @nowebref */ @@ -131,11 +147,17 @@ public XML(Reader reader) throws IOException, ParserConfigurationException, SAXE /** - * Advanced users only; see loadXML() in PApplet. + * Advanced users only; use loadXML() in PApplet. + * + * Added extra code to handle \u2028 (Unicode NLF), which is sometimes + * inserted by web browsers (Safari?) and not distinguishable from a "real" + * LF (or CRLF) in some text editors (i.e. TextEdit on OS X). Only doing + * this for XML (and not all Reader objects) because LFs are essential. + * https://github.com/processing/processing/issues/2100 * * @nowebref */ - public XML(Reader reader, String options) throws IOException, ParserConfigurationException, SAXException { + public XML(final Reader reader, String options) throws IOException, ParserConfigurationException, SAXException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // Prevent 503 errors from www.w3.org @@ -164,17 +186,24 @@ public XML(Reader reader, String options) throws IOException, ParserConfiguratio // builder = new SAXBuilder(); // builder.setValidation(validating); -// print(dataPath("1broke.html"), System.out); + Document document = builder.parse(new InputSource(new Reader() { + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int count = reader.read(cbuf, off, len); + for (int i = 0; i < count; i++) { + if (cbuf[off+i] == '\u2028') { + cbuf[off+i] = '\n'; + } + } + return count; + } -// Document document = builder.parse(dataPath("1_alt.html")); - Document document = builder.parse(new InputSource(reader)); + @Override + public void close() throws IOException { + reader.close(); + } + })); node = document.getDocumentElement(); -// name = node.getNodeName(); - -// NodeList nodeList = document.getDocumentElement().getChildNodes(); -// for (int i = 0; i < nodeList.getLength(); i++) { -// } -// print(createWriter("data/1_alt_reparse.html"), document.getDocumentElement(), 0); } @@ -202,7 +231,18 @@ public XML(String name) { protected XML(XML parent, Node node) { this.node = node; this.parent = parent; -// this.name = node.getNodeName(); + + for (String attr : parent.listAttributes()) { + if (attr.startsWith("xmlns")) { + // Copy namespace attributes to the kids, otherwise this XML + // can no longer be printed (or manipulated in most ways). + // Only do this when it's an Element, otherwise it's trying to set + // attributes on text notes (interstitial content). + if (node instanceof Element) { + setString(attr, parent.getString(attr)); + } + } + } } @@ -233,6 +273,11 @@ static public XML parse(String data, String options) throws IOException, ParserC // } + public boolean save(File file) { + return save(file, null); + } + + public boolean save(File file, String options) { PrintWriter writer = PApplet.createWriter(file); boolean result = write(writer); @@ -408,7 +453,7 @@ public XML getChild(int index) { * Get a child by its name or path. * * @param name element name or path/to/element - * @return the first matching element + * @return the first matching element or null if no match */ public XML getChild(String name) { if (name.length() > 0 && name.charAt(0) == '/') { @@ -549,6 +594,32 @@ public void removeChild(XML kid) { children = null; // TODO not efficient } + /** + * Removes whitespace nodes. + * Those whitespace nodes are required to reconstruct the original XML's spacing and indentation. + * If you call this and use saveXML() your original spacing will be gone. + * + * @nowebref + * @brief Removes whitespace nodes + */ + public void trim() { + try { + XPathFactory xpathFactory = XPathFactory.newInstance(); + XPathExpression xpathExp = + xpathFactory.newXPath().compile("//text()[normalize-space(.) = '']"); + NodeList emptyTextNodes = (NodeList) + xpathExp.evaluate(node, XPathConstants.NODESET); + + // Remove each empty text node from document. + for (int i = 0; i < emptyTextNodes.getLength(); i++) { + Node emptyTextNode = emptyTextNodes.item(i); + emptyTextNode.getParentNode().removeChild(emptyTextNode); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + // /** Remove whitespace nodes. */ // public void trim() { @@ -670,8 +741,14 @@ public String getString(String name) { public String getString(String name, String defaultValue) { - Node attr = node.getAttributes().getNamedItem(name); - return (attr == null) ? defaultValue : attr.getNodeValue(); + NamedNodeMap attrs = node.getAttributes(); + if (attrs != null) { + Node attr = attrs.getNamedItem(name); + if (attr != null) { + return attr.getNodeValue(); + } + } + return defaultValue; } @@ -1035,10 +1112,18 @@ public String format(int indent) { String outgoing = stringWriter.toString(); // Add the XML declaration to the top if it's not there already - if (!outgoing.startsWith(decl)) { - return decl + sep + outgoing; - } else { + if (outgoing.startsWith(decl)) { + int declen = decl.length(); + int seplen = sep.length(); + if (outgoing.length() > declen + seplen && + !outgoing.substring(declen, declen + seplen).equals(sep)) { + // make sure there's a line break between the XML decl and the code + return outgoing.substring(0, decl.length()) + + sep + outgoing.substring(decl.length()); + } return outgoing; + } else { + return decl + sep + outgoing; } } catch (Exception e) { @@ -1048,6 +1133,11 @@ public String format(int indent) { } + public void print() { + PApplet.println(format(2)); + } + + /** * Return the XML document formatted with two spaces for indents. * Chosen to do this since it's the most common case (e.g. with println()). diff --git a/core/src/processing/event/Event.java b/libs/processing-core/src/main/java/processing/event/Event.java similarity index 98% rename from core/src/processing/event/Event.java rename to libs/processing-core/src/main/java/processing/event/Event.java index 32bf175c2..109dbbee7 100644 --- a/core/src/processing/event/Event.java +++ b/libs/processing-core/src/main/java/processing/event/Event.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/core/src/processing/event/KeyEvent.java b/libs/processing-core/src/main/java/processing/event/KeyEvent.java similarity index 74% rename from core/src/processing/event/KeyEvent.java rename to libs/processing-core/src/main/java/processing/event/KeyEvent.java index 10bbba92a..4a9bdf44d 100644 --- a/core/src/processing/event/KeyEvent.java +++ b/libs/processing-core/src/main/java/processing/event/KeyEvent.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -31,6 +31,7 @@ public class KeyEvent extends Event { char key; int keyCode; + boolean isAutoRepeat; public KeyEvent(Object nativeObject, long millis, int action, int modifiers, @@ -42,6 +43,17 @@ public KeyEvent(Object nativeObject, } + public KeyEvent(Object nativeObject, + long millis, int action, int modifiers, + char key, int keyCode, boolean isAutoRepeat) { + super(nativeObject, millis, action, modifiers); + this.flavor = KEY; + this.key = key; + this.keyCode = keyCode; + this.isAutoRepeat = isAutoRepeat; + } + + public char getKey() { return key; } @@ -50,4 +62,8 @@ public char getKey() { public int getKeyCode() { return keyCode; } + + public boolean isAutoRepeat() { + return isAutoRepeat; + } } \ No newline at end of file diff --git a/core/src/processing/event/MouseEvent.java b/libs/processing-core/src/main/java/processing/event/MouseEvent.java similarity index 71% rename from core/src/processing/event/MouseEvent.java rename to libs/processing-core/src/main/java/processing/event/MouseEvent.java index 408ef2e95..012f4e29e 100644 --- a/core/src/processing/event/MouseEvent.java +++ b/libs/processing-core/src/main/java/processing/event/MouseEvent.java @@ -3,7 +3,7 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012 The Processing Foundation + Copyright (c) 2012-21 The Processing Foundation This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -36,7 +36,7 @@ public class MouseEvent extends Event { protected int x, y; protected int button; - protected int clickCount; + protected int count; // public MouseEvent(int x, int y) { @@ -48,13 +48,13 @@ public class MouseEvent extends Event { public MouseEvent(Object nativeObject, long millis, int action, int modifiers, - int x, int y, int button, int clickCount) { + int x, int y, int button, int count) { super(nativeObject, millis, action, modifiers); this.flavor = MOUSE; this.x = x; this.y = y; this.button = button; - this.clickCount = clickCount; + this.count = count; } @@ -79,8 +79,23 @@ public int getButton() { // } + @Deprecated public int getClickCount() { - return clickCount; + return count; + } + + + /** + * Number of clicks for mouse button events, or the number of steps (positive + * or negative depending on direction) for a mouse wheel event. + * Wheel events follow Java (see here), so + * getAmount() will return "negative values if the mouse wheel was rotated + * up or away from the user" and positive values in the other direction. + * On Mac OS X, this will be reversed when "natural" scrolling is enabled + * in System Preferences &rarr Mouse. + */ + public int getCount() { + return count; } diff --git a/libs/processing-core/src/main/java/processing/event/TouchEvent.java b/libs/processing-core/src/main/java/processing/event/TouchEvent.java new file mode 100644 index 000000000..440a7824e --- /dev/null +++ b/libs/processing-core/src/main/java/processing/event/TouchEvent.java @@ -0,0 +1,142 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.event; + +/* + * IMPORTANT NOTICE: None of the methods and inner classes in TouchEvent are + * part of the Processing API. Don't use them! They might be changed or removed + * without notice. +*/ +public class TouchEvent extends Event { + static public final int START = 1; + static public final int END = 2; + static public final int CANCEL = 3; + static public final int MOVE = 4; + + protected int action; + + protected int button; + protected int numPointers; + protected int[] pointerId; + protected float[] pointerX; + protected float[] pointerY; + protected float[] pointerArea; + protected float[] pointerPressure; + + public TouchEvent(Object nativeObject, long millis, int action, int modifiers, + int button) { + super(nativeObject, millis, action, modifiers); + this.flavor = TOUCH; + this.button = button; + } + + public void setNumPointers(int n) { + numPointers = n; + pointerId = new int[n]; + pointerX = new float[n]; + pointerY = new float[n]; + pointerArea = new float[n]; + pointerPressure = new float[n]; + } + + + public void setPointer(int idx, int id, float x, float y, float a, float p) { + pointerId[idx] = id; + pointerX[idx] = x; + pointerY[idx] = y; + pointerArea[idx] = a; + pointerPressure[idx] = p; + } + + + public int getNumPointers() { + return numPointers; + } + + + public Pointer getPointer(int idx) { + Pointer pt = new Pointer(); + pt.id = pointerId[idx]; + pt.x = pointerX[idx]; + pt.y = pointerY[idx]; + pt.area = pointerArea[idx]; + pt.pressure = pointerPressure[idx]; + return pt; + } + + + public int getPointerId(int idx) { + return pointerId[idx]; + } + + + public float getPointerX(int idx) { + return pointerX[idx]; + } + + + public float getPointerY(int idx) { + return pointerY[idx]; + } + + + public float getPointerArea(int idx) { + return pointerArea[idx]; + } + + + public float getPointerPressure(int idx) { + return pointerPressure[idx]; + } + + + public int getButton() { + return button; + } + + + public Pointer[] getTouches(Pointer[] touches) { + if (touches == null || touches.length != numPointers) { + touches = new Pointer[numPointers]; + for (int idx = 0; idx < numPointers; idx++) { + touches[idx] = new Pointer(); + } + } + for (int idx = 0; idx < numPointers; idx++) { + touches[idx].id = pointerId[idx]; + touches[idx].x = pointerX[idx]; + touches[idx].y = pointerY[idx]; + touches[idx].area = pointerArea[idx]; + touches[idx].pressure = pointerPressure[idx]; + } + return touches; + } + + + public class Pointer { + public int id; + public float x, y; + public float area; + public float pressure; + } +} \ No newline at end of file diff --git a/core/src/processing/opengl/FontTexture.java b/libs/processing-core/src/main/java/processing/opengl/FontTexture.java similarity index 84% rename from core/src/processing/opengl/FontTexture.java rename to libs/processing-core/src/main/java/processing/opengl/FontTexture.java index 8c1c66f5d..cc67259bb 100644 --- a/core/src/processing/opengl/FontTexture.java +++ b/libs/processing-core/src/main/java/processing/opengl/FontTexture.java @@ -3,12 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-12 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -58,12 +59,10 @@ class FontTexture implements PConstants { protected int lineHeight; protected Texture[] textures = null; protected PImage[] images = null; - protected int currentTex; protected int lastTex; protected TextureInfo[] glyphTexinfos; protected HashMap texinfoMap; - public FontTexture(PGraphicsOpenGL pg, PFont font, boolean is3D) { pgl = pg.pgl; this.is3D = is3D; @@ -86,7 +85,6 @@ protected void dispose() { protected void initTexture(PGraphicsOpenGL pg, PFont font) { - currentTex = -1; lastTex = -1; int spow = PGL.nextPowerOfTwo(font.getSize()); @@ -117,10 +115,10 @@ public boolean addTexture(PGraphicsOpenGL pg) { boolean resize; w = maxSize; - if (-1 < currentTex && textures[currentTex].glHeight < maxSize) { + if (-1 < lastTex && textures[lastTex].glHeight < maxSize) { // The height of the current texture is less than the maximum, this // means we can replace it with a larger texture. - h = PApplet.min(2 * textures[currentTex].glHeight, maxSize); + h = PApplet.min(2 * textures[lastTex].glHeight, maxSize); resize = true; } else { h = minSize; @@ -147,32 +145,31 @@ public boolean addTexture(PGraphicsOpenGL pg) { textures[0] = tex; images = new PImage[1]; images[0] = pg.wrapTexture(tex); - currentTex = 0; + lastTex = 0; } else if (resize) { // Replacing old smaller texture with larger one. // But first we must copy the contents of the older // texture into the new one. - Texture tex0 = textures[currentTex]; + Texture tex0 = textures[lastTex]; tex.put(tex0); - textures[currentTex] = tex; + textures[lastTex] = tex; - pg.setCache(images[currentTex], tex); - images[currentTex].width = tex.width; - images[currentTex].height = tex.height; + pg.setCache(images[lastTex], tex); + images[lastTex].width = tex.width; + images[lastTex].height = tex.height; } else { // Adding new texture to the list. - Texture[] tempTex = textures; - textures = new Texture[textures.length + 1]; - PApplet.arrayCopy(tempTex, textures, tempTex.length); - textures[tempTex.length] = tex; - currentTex = textures.length - 1; - - PImage[] tempImg = images; - images = new PImage[textures.length]; - PApplet.arrayCopy(tempImg, images, tempImg.length); - images[tempImg.length] = pg.wrapTexture(tex); + lastTex = textures.length; + Texture[] tempTex = new Texture[lastTex + 1]; + PApplet.arrayCopy(textures, tempTex, textures.length); + tempTex[lastTex] = tex; + textures = tempTex; + + PImage[] tempImg = new PImage[textures.length]; + PApplet.arrayCopy(images, tempImg, images.length); + tempImg[lastTex] = pg.wrapTexture(tex); + images = tempImg; } - lastTex = currentTex; // Make sure that the current texture is bound. tex.bind(); @@ -182,7 +179,6 @@ public boolean addTexture(PGraphicsOpenGL pg) { public void begin() { - setTexture(0); } @@ -193,23 +189,8 @@ public void end() { } - public void setTexture(int idx) { - if (0 <= idx && idx < textures.length) { - currentTex = idx; - } - } - - - public PImage getTexture(int idx) { - if (0 <= idx && idx < images.length) { - return images[idx]; - } - return null; - } - - - public PImage getCurrentTexture() { - return getTexture(currentTex); + public PImage getTexture(TextureInfo info) { + return images[info.texIndex]; } @@ -226,7 +207,7 @@ public void updateGlyphsTexCoords() { // loop over current glyphs. for (int i = 0; i < glyphTexinfos.length; i++) { TextureInfo tinfo = glyphTexinfos[i]; - if (tinfo != null && tinfo.texIndex == currentTex) { + if (tinfo != null && tinfo.texIndex == lastTex) { tinfo.updateUV(); } } @@ -258,14 +239,20 @@ public boolean contextIsOutdated() { } if (outdated) { for (int i = 0; i < textures.length; i++) { - PGraphicsOpenGL.removeTextureObject(textures[i].glName, - textures[i].context); - textures[i].glName = 0; + textures[i].dispose(); } } return outdated; } +// public void draw() { +// Texture tex = textures[lastTex]; +// pgl.drawTexture(tex.glTarget, tex.glName, +// tex.glWidth, tex.glHeight, +// 0, 0, tex.glWidth, tex.glHeight); +// } + + // Adds this glyph to the opengl texture in PFont. protected void addToTexture(PGraphicsOpenGL pg, int idx, PFont.Glyph glyph) { // We add one pixel to avoid issues when sampling the font texture at @@ -309,16 +296,15 @@ protected void addToTexture(PGraphicsOpenGL pg, int idx, PFont.Glyph glyph) { } // Is there room for this glyph in the current line? - if (offsetX + w > textures[currentTex].glWidth) { + if (offsetX + w > textures[lastTex].glWidth) { // No room, go to the next line: offsetX = 0; offsetY += lineHeight; - lineHeight = 0; } lineHeight = Math.max(lineHeight, h); boolean resized = false; - if (offsetY + lineHeight > textures[currentTex].glHeight) { + if (offsetY + lineHeight > textures[lastTex].glHeight) { // We run out of space in the current texture, so we add a new texture: resized = addTexture(pg); if (resized) { @@ -334,8 +320,7 @@ protected void addToTexture(PGraphicsOpenGL pg, int idx, PFont.Glyph glyph) { } } - TextureInfo tinfo = new TextureInfo(currentTex, offsetX, offsetY, - w, h, rgba); + TextureInfo tinfo = new TextureInfo(lastTex, offsetX, offsetY, w, h, rgba); offsetX += w; if (idx == glyphTexinfos.length) { @@ -378,6 +363,7 @@ class TextureInfo { void updateUV() { width = textures[texIndex].glWidth; height = textures[texIndex].glHeight; + u0 = (float)crop[0] / (float)width; u1 = u0 + (float)crop[2] / (float)width; v0 = (float)(crop[1] + crop[3]) / (float)height; @@ -390,4 +376,4 @@ void updateTex() { crop[2] + 2, -crop[3] + 2); } } -} \ No newline at end of file +} diff --git a/core/src/processing/opengl/FrameBuffer.java b/libs/processing-core/src/main/java/processing/opengl/FrameBuffer.java similarity index 82% rename from core/src/processing/opengl/FrameBuffer.java rename to libs/processing-core/src/main/java/processing/opengl/FrameBuffer.java index 31d50f559..a5426c52d 100644 --- a/core/src/processing/opengl/FrameBuffer.java +++ b/libs/processing-core/src/main/java/processing/opengl/FrameBuffer.java @@ -3,12 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-12 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -25,6 +26,7 @@ import processing.core.PApplet; import processing.core.PConstants; +import processing.opengl.PGraphicsOpenGL.GLResourceFrameBuffer; import java.nio.IntBuffer; @@ -51,6 +53,7 @@ public class FrameBuffer implements PConstants { public int glMultisample; public int width; public int height; + private GLResourceFrameBuffer glres; protected int depthBits; protected int stencilBits; @@ -148,31 +151,6 @@ public class FrameBuffer implements PConstants { } - @Override - protected void finalize() throws Throwable { - try { - if (!screenFb) { - if (glFbo != 0) { - PGraphicsOpenGL.finalizeFrameBufferObject(glFbo, context); - } - if (glDepth != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glDepth, context); - } - if (glStencil != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glStencil, context); - } - if (glMultisample != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glMultisample, context); - } - if (glDepthStencil != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glDepthStencil, context); - } - } - } finally { - super.finalize(); - } - } - public void clear() { pg.pushFramebuffer(); pg.setFramebuffer(this); @@ -353,26 +331,23 @@ protected void allocate() { dispose(); // Just in the case this object is being re-allocated. context = pgl.getCurrentContext(); + glres = new GLResourceFrameBuffer(this); // create the FBO resources... if (screenFb) { glFbo = 0; } else { - //create the FBO object... - glFbo = PGraphicsOpenGL.createFrameBufferObject(context, pgl); - - // ... and then create the rest of the stuff. if (multisample) { - createColorBufferMultisample(); + initColorBufferMultisample(); } if (packedDepthStencil) { - createPackedDepthStencilBuffer(); + initPackedDepthStencilBuffer(); } else { if (0 < depthBits) { - createDepthBuffer(); + initDepthBuffer(); } if (0 < stencilBits) { - createStencilBuffer(); + initStencilBuffer(); } } } @@ -381,26 +356,14 @@ protected void allocate() { protected void dispose() { if (screenFb) return; - - if (glFbo != 0) { - PGraphicsOpenGL.finalizeFrameBufferObject(glFbo, context); + if (glres != null) { + glres.dispose(); glFbo = 0; - } - if (glDepth != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glDepth, context); glDepth = 0; - } - if (glStencil != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glStencil, context); glStencil = 0; - } - if (glMultisample != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glMultisample, context); glMultisample = 0; - } - if (glDepthStencil != 0) { - PGraphicsOpenGL.finalizeRenderBufferObject(glDepthStencil, context); glDepthStencil = 0; + glres = null; } } @@ -410,18 +373,7 @@ protected boolean contextIsOutdated() { boolean outdated = !pgl.contextIsCurrent(context); if (outdated) { - PGraphicsOpenGL.removeFrameBufferObject(glFbo, context); - PGraphicsOpenGL.removeRenderBufferObject(glDepth, context); - PGraphicsOpenGL.removeRenderBufferObject(glStencil, context); - PGraphicsOpenGL.removeRenderBufferObject(glDepthStencil, context); - PGraphicsOpenGL.removeRenderBufferObject(glMultisample, context); - - glFbo = 0; - glDepth = 0; - glStencil = 0; - glDepthStencil = 0; - glMultisample = 0; - + dispose(); for (int i = 0; i < numColorBuffers; i++) { colorBufferTex[i] = null; } @@ -430,13 +382,12 @@ protected boolean contextIsOutdated() { } - protected void createColorBufferMultisample() { + protected void initColorBufferMultisample() { if (screenFb) return; pg.pushFramebuffer(); pg.setFramebuffer(this); - glMultisample = PGraphicsOpenGL.createRenderBufferObject(context, pgl); pgl.bindRenderbuffer(PGL.RENDERBUFFER, glMultisample); pgl.renderbufferStorageMultisample(PGL.RENDERBUFFER, nsamples, PGL.RGBA8, width, height); @@ -447,7 +398,7 @@ protected void createColorBufferMultisample() { } - protected void createPackedDepthStencilBuffer() { + protected void initPackedDepthStencilBuffer() { if (screenFb) return; if (width == 0 || height == 0) { @@ -457,7 +408,6 @@ protected void createPackedDepthStencilBuffer() { pg.pushFramebuffer(); pg.setFramebuffer(this); - glDepthStencil = PGraphicsOpenGL.createRenderBufferObject(context, pgl); pgl.bindRenderbuffer(PGL.RENDERBUFFER, glDepthStencil); if (multisample) { @@ -477,7 +427,7 @@ protected void createPackedDepthStencilBuffer() { } - protected void createDepthBuffer() { + protected void initDepthBuffer() { if (screenFb) return; if (width == 0 || height == 0) { @@ -487,7 +437,6 @@ protected void createDepthBuffer() { pg.pushFramebuffer(); pg.setFramebuffer(this); - glDepth = PGraphicsOpenGL.createRenderBufferObject(context, pgl); pgl.bindRenderbuffer(PGL.RENDERBUFFER, glDepth); int glConst = PGL.DEPTH_COMPONENT16; @@ -513,7 +462,7 @@ protected void createDepthBuffer() { } - protected void createStencilBuffer() { + protected void initStencilBuffer() { if (screenFb) return; if (width == 0 || height == 0) { @@ -523,7 +472,6 @@ protected void createStencilBuffer() { pg.pushFramebuffer(); pg.setFramebuffer(this); - glStencil = PGraphicsOpenGL.createRenderBufferObject(context, pgl); pgl.bindRenderbuffer(PGL.RENDERBUFFER, glStencil); int glConst = PGL.STENCIL_INDEX1; diff --git a/core/src/processing/opengl/LinePath.java b/libs/processing-core/src/main/java/processing/opengl/LinePath.java similarity index 100% rename from core/src/processing/opengl/LinePath.java rename to libs/processing-core/src/main/java/processing/opengl/LinePath.java diff --git a/core/src/processing/opengl/LineStroker.java b/libs/processing-core/src/main/java/processing/opengl/LineStroker.java similarity index 100% rename from core/src/processing/opengl/LineStroker.java rename to libs/processing-core/src/main/java/processing/opengl/LineStroker.java diff --git a/core/src/processing/opengl/PGL.java b/libs/processing-core/src/main/java/processing/opengl/PGL.java similarity index 69% rename from core/src/processing/opengl/PGL.java rename to libs/processing-core/src/main/java/processing/opengl/PGL.java index c0946b903..18e4d8bb8 100644 --- a/core/src/processing/opengl/PGL.java +++ b/libs/processing-core/src/main/java/processing/opengl/PGL.java @@ -3,12 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -23,9 +24,6 @@ package processing.opengl; -import processing.core.PApplet; -import processing.core.PGraphics; - import java.io.IOException; import java.net.URL; import java.nio.Buffer; @@ -35,6 +33,12 @@ import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.Arrays; +import java.util.regex.Pattern; + +import processing.core.PApplet; +import processing.core.PConstants; +import processing.core.PGraphics; + /** * Processing-OpenGL abstraction layer. Needs to be implemented by subclasses @@ -48,8 +52,9 @@ public abstract class PGL { // Basic fields - /** The PGraphics object using this interface */ - protected PGraphicsOpenGL pg; + /** The PGraphics and PApplet objects using this interface */ + protected PGraphicsOpenGL graphics; + protected PApplet sketch; /** OpenGL thread */ protected Thread glThread; @@ -64,22 +69,14 @@ public abstract class PGL { // Parameters - protected static boolean USE_FBOLAYER_BY_DEFAULT = false; - protected static int REQUESTED_DEPTH_BITS = 24; - protected static int REQUESTED_STENCIL_BITS = 8; - protected static int REQUESTED_ALPHA_BITS = 8; + public static int REQUESTED_DEPTH_BITS = 24; + public static int REQUESTED_STENCIL_BITS = 8; + public static int REQUESTED_ALPHA_BITS = 8; /** Switches between the use of regular and direct buffers. */ protected static boolean USE_DIRECT_BUFFERS = true; protected static int MIN_DIRECT_BUFFER_SIZE = 1; - /** This flag enables/disables a hack to make sure that anything drawn - * in setup will be maintained even a renderer restart (e.g.: smooth change). - * See the code and comments involving this constant in - * PGraphicsOpenGL.endDraw(). - */ - protected static boolean SAVE_SURFACE_TO_PIXELS_HACK = true; - /** Enables/disables mipmap use. */ protected static boolean MIPMAPS_ENABLED = true; @@ -132,21 +129,35 @@ public abstract class PGL { // ........................................................ + // Variables to handle single-buffered situations (i.e.: Android) + + protected IntBuffer firstFrame; + protected static boolean SINGLE_BUFFERED = false; + + // ........................................................ + // FBO layer - protected boolean fboLayerRequested = false; + protected boolean fboLayerEnabled = false; protected boolean fboLayerCreated = false; - protected boolean fboLayerInUse = false; - protected boolean firstFrame = true; - protected int reqNumSamples; + protected boolean fboLayerEnabledReq = false; + protected boolean fboLayerDisableReq = false; + protected boolean fbolayerResetReq = false; + public int reqNumSamples; protected int numSamples; + protected IntBuffer glColorFbo; - protected IntBuffer glMultiFbo; - protected IntBuffer glColorBuf; protected IntBuffer glColorTex; protected IntBuffer glDepthStencil; protected IntBuffer glDepth; protected IntBuffer glStencil; + + protected IntBuffer glMultiFbo; + protected IntBuffer glMultiColor; + protected IntBuffer glMultiDepthStencil; + protected IntBuffer glMultiDepth; + protected IntBuffer glMultiStencil; + protected int fboWidth, fboHeight; protected int backTex, frontTex; @@ -249,38 +260,62 @@ public abstract class PGL { protected FloatBuffer depthBuffer; protected ByteBuffer stencilBuffer; + //........................................................ + + // Rendering information + + /** Used to register amount of geometry rendered in each frame. */ + protected int geomCount = 0; + protected int pgeomCount; + + /** Used to register calls to background. */ + protected boolean clearColor = false; + protected boolean pclearColor; + + protected boolean clearDepth = false; + protected boolean pclearDepth; + + protected boolean clearStencil = false; + protected boolean pclearStencil; + + // ........................................................ // Error messages - protected static final String WIKI = + public static final String WIKI = " Read http://wiki.processing.org/w/OpenGL_Issues for help."; - protected static final String FRAMEBUFFER_ERROR = + public static final String FRAMEBUFFER_ERROR = "Framebuffer error (%1$s), rendering will probably not work as expected" + WIKI; - protected static final String MISSING_FBO_ERROR = + public static final String MISSING_FBO_ERROR = "Framebuffer objects are not supported by this hardware (or driver)" + WIKI; - protected static final String MISSING_GLSL_ERROR = + public static final String MISSING_GLSL_ERROR = "GLSL shaders are not supported by this hardware (or driver)" + WIKI; - protected static final String MISSING_GLFUNC_ERROR = + public static final String MISSING_GLFUNC_ERROR = "GL function %1$s is not available on this hardware (or driver)" + WIKI; - protected static final String UNSUPPORTED_GLPROF_ERROR = + public static final String UNSUPPORTED_GLPROF_ERROR = "Unsupported OpenGL profile."; - protected static final String TEXUNIT_ERROR = + public static final String TEXUNIT_ERROR = "Number of texture units not supported by this hardware (or driver)" + WIKI; - protected static final String NONPRIMARY_ERROR = + public static final String NONPRIMARY_ERROR = "The renderer is trying to call a PGL function that can only be called on a primary PGL. " + "This is most likely due to a bug in the renderer's code, please report it with an " + "issue on Processing's github page https://github.com/processing/processing/issues?state=open " + "if using any of the built-in OpenGL renderers. If you are using a contributed " + "library, contact the library's developers."; + protected static final String DEPTH_READING_NOT_ENABLED_ERROR = + "Reading depth and stencil values from this multisampled buffer is not enabled. " + + "You can enable it by calling hint(ENABLE_DEPTH_READING) once. " + + "If your sketch becomes too slow, disable multisampling with noSmooth() instead."; + // ........................................................ // Constants @@ -314,6 +349,40 @@ public abstract class PGL { protected static boolean BIG_ENDIAN = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; + // ........................................................ + + // Present mode + + // ........................................................ + + // Present mode + + protected boolean presentMode = false; + protected boolean showStopButton = true; + public float presentX; + public float presentY; + protected IntBuffer closeButtonTex; + protected int stopButtonColor; + protected int stopButtonWidth = 28; + protected int stopButtonHeight = 12; + protected int stopButtonX = 21; // The position of the close button is relative to the + protected int closeButtonY = 21; // lower left corner + protected static int[] closeButtonPix = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, -1, -1, -1, 0, 0, 0, -1, -1, -1, -1, -1, 0, 0, 0, -1, + -1, -1, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, + 0, 0, 0, -1, -1, 0, -1, -1, 0, 0, -1, -1, 0, -1, -1, 0, 0, -1, 0, 0, 0, 0, 0, + 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, -1, + -1, -1, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, + 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, + 0, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, -1, 0, -1, + -1, 0, 0, -1, -1, 0, -1, -1, 0, 0, 0, -1, -1, -1, 0, 0, 0, 0, 0, -1, -1, -1, + 0, 0, 0, -1, -1, -1, 0, 0, 0, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0}; + /////////////////////////////////////////////////////////////// @@ -324,19 +393,19 @@ public PGL() { } public PGL(PGraphicsOpenGL pg) { - this.pg = pg; + this.graphics = pg; if (glColorTex == null) { - glColorTex = allocateIntBuffer(2); glColorFbo = allocateIntBuffer(1); - glMultiFbo = allocateIntBuffer(1); - glColorBuf = allocateIntBuffer(1); + glColorTex = allocateIntBuffer(2); glDepthStencil = allocateIntBuffer(1); glDepth = allocateIntBuffer(1); glStencil = allocateIntBuffer(1); - fboLayerCreated = false; - fboLayerInUse = false; - firstFrame = false; + glMultiFbo = allocateIntBuffer(1); + glMultiColor = allocateIntBuffer(1); + glMultiDepthStencil = allocateIntBuffer(1); + glMultiDepth = allocateIntBuffer(1); + glMultiStencil = allocateIntBuffer(1); } byteBuffer = allocateByteBuffer(1); @@ -345,98 +414,117 @@ public PGL(PGraphicsOpenGL pg) { } + public void dispose() { + destroyFBOLayer(); + graphics = null; + sketch = null; + } + + public void setPrimary(boolean primary) { primaryPGL = primary; } - /** - * Return the native canvas the OpenGL context associated to this PGL object - * is rendering to (if any). - */ - public abstract Object getCanvas(); + static public int smoothToSamples(int smooth) { + if (smooth == 0) { + // smooth(0) is noSmooth(), which is 1x sampling + return 1; + } else if (smooth == 1) { + // smooth(1) means "default smoothing", which is 2x for OpenGL + return 2; + } else { + // smooth(N) can be used for 4x, 8x, etc + return smooth; + } + } - protected abstract void setFps(float fps); + abstract public Object getNative(); - protected abstract void initSurface(int antialias); + public void setFrameRate(float fps) { + targetFps = fps; + currentFps = fps; + setFps = true; + } - protected abstract void reinitSurface(); + public float getFrameRate() { + return currentFps; + } - protected abstract void registerListeners(); + abstract protected void initSurface(int antialias); - protected void deleteSurface() { - if (threadIsCurrent() && fboLayerCreated) { - deleteTextures(2, glColorTex); - deleteFramebuffers(1, glColorFbo); - deleteFramebuffers(1, glMultiFbo); - deleteRenderbuffers(1, glColorBuf); - deleteRenderbuffers(1, glDepthStencil); - deleteRenderbuffers(1, glDepth); - deleteRenderbuffers(1, glStencil); - } + abstract protected void reinitSurface(); - fboLayerCreated = false; - fboLayerInUse = false; - firstFrame = false; - } + + abstract protected void registerListeners(); protected int getReadFramebuffer() { - return fboLayerInUse ? glColorFbo.get(0) : 0; + return fboLayerEnabled ? glColorFbo.get(0) : 0; } protected int getDrawFramebuffer() { - if (fboLayerInUse) return 1 < numSamples ? glMultiFbo.get(0) : - glColorFbo.get(0); + if (fboLayerEnabled) return 1 < numSamples ? glMultiFbo.get(0) : + glColorFbo.get(0); else return 0; } protected int getDefaultDrawBuffer() { - return fboLayerInUse ? COLOR_ATTACHMENT0 : FRONT; + return fboLayerEnabled ? COLOR_ATTACHMENT0 : BACK; } protected int getDefaultReadBuffer() { - return fboLayerInUse ? COLOR_ATTACHMENT0 : FRONT; + return fboLayerEnabled ? COLOR_ATTACHMENT0 : FRONT; } protected boolean isFBOBacked() {; - return fboLayerInUse; + return fboLayerEnabled; } - protected void requestFBOLayer() { - fboLayerRequested = true; + @Deprecated + public void requestFBOLayer() { + enableFBOLayer(); } - protected boolean isMultisampled() { - return 1 < numSamples; + public void enableFBOLayer() { + fboLayerEnabledReq = true; } - protected int getDepthBits() { - intBuffer.rewind(); - getIntegerv(DEPTH_BITS, intBuffer); - return intBuffer.get(0); + public void disableFBOLayer() { + fboLayerDisableReq = true; } - protected int getStencilBits() { - intBuffer.rewind(); - getIntegerv(STENCIL_BITS, intBuffer); - return intBuffer.get(0); + public void resetFBOLayer() { + fbolayerResetReq = true; + } + + + abstract public void queueEvent(Runnable runnable); + + protected boolean isMultisampled() { + return 1 < numSamples; } + abstract protected int getDepthBits(); + + + abstract protected int getStencilBits(); + + protected boolean getDepthTest() { intBuffer.rewind(); getBooleanv(DEPTH_TEST, intBuffer); @@ -453,14 +541,14 @@ protected boolean getDepthWriteMask() { protected Texture wrapBackTexture(Texture texture) { if (texture == null) { - texture = new Texture(pg); - texture.init(pg.width, pg.height, + texture = new Texture(graphics); + texture.init(graphics.width, graphics.height, glColorTex.get(backTex), TEXTURE_2D, RGBA, fboWidth, fboHeight, NEAREST, NEAREST, CLAMP_TO_EDGE, CLAMP_TO_EDGE); - texture.invertedY(true); + texture.invertedY(!graphics.cameraUp); texture.colorBuffer(true); - pg.setCache(pg, texture); + graphics.setCache(graphics, texture); } else { texture.glName = glColorTex.get(backTex); } @@ -470,12 +558,12 @@ protected Texture wrapBackTexture(Texture texture) { protected Texture wrapFrontTexture(Texture texture) { if (texture == null) { - texture = new Texture(pg); - texture.init(pg.width, pg.height, + texture = new Texture(graphics); + texture.init(graphics.width, graphics.height, glColorTex.get(frontTex), TEXTURE_2D, RGBA, fboWidth, fboHeight, NEAREST, NEAREST, CLAMP_TO_EDGE, CLAMP_TO_EDGE); - texture.invertedY(true); + texture.invertedY(!graphics.cameraUp); texture.colorBuffer(true); } else { texture.glName = glColorTex.get(frontTex); @@ -513,22 +601,148 @@ protected void syncBackTexture() { if (1 < numSamples) { bindFramebufferImpl(READ_FRAMEBUFFER, glMultiFbo.get(0)); bindFramebufferImpl(DRAW_FRAMEBUFFER, glColorFbo.get(0)); + int mask = COLOR_BUFFER_BIT; + if (graphics.getHint(PConstants.ENABLE_BUFFER_READING)) { + mask |= DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT; + } blitFramebuffer(0, 0, fboWidth, fboHeight, 0, 0, fboWidth, fboHeight, - COLOR_BUFFER_BIT, NEAREST); + mask, NEAREST); } } + abstract protected float getPixelScale(); + + /////////////////////////////////////////////////////////// + + // Present mode + + + public void initPresentMode(float x, float y, int stopColor) { + presentMode = true; + showStopButton = stopColor != 0; + stopButtonColor = stopColor; + presentX = x; + presentY = y; + enableFBOLayer(); + } + + + public boolean presentMode() { + return presentMode; + } + + + public float presentX() { + return presentX; + } + + + public float presentY() { + return presentY; + } + + + public boolean insideStopButton(float x, float y) { + if (!showStopButton) return false; + return stopButtonX < x && x < stopButtonX + stopButtonWidth && + -(closeButtonY + stopButtonHeight) < y && y < -closeButtonY; + } + + /////////////////////////////////////////////////////////// // Frame rendering - protected void beginDraw(boolean clear0) { - if (needFBOLayer(clear0)) { - if (!fboLayerCreated) createFBOLayer(); + protected void clearDepthStencil() { + if (!pclearDepth && !pclearStencil) { + depthMask(true); + clearDepth(1); + clearStencil(0); + clear(DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT); + } else if (!pclearDepth) { + depthMask(true); + clearDepth(1); + clear(DEPTH_BUFFER_BIT); + } else if (!pclearStencil) { + clearStencil(0); + clear(STENCIL_BUFFER_BIT); + } + } + + + protected void clearBackground(float r, float g, float b, float a, + boolean depth, boolean stencil) { + clearColor(r, g, b, a); + if (depth && stencil) { + clearDepth(1); + clearStencil(0); + clear(DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT); + if (0 < sketch.frameCount) { + clearColor = true; + clearDepth = true; + clearStencil = true; + } + } else if (depth) { + clearDepth(1); + clear(DEPTH_BUFFER_BIT | COLOR_BUFFER_BIT); + if (0 < sketch.frameCount) { + clearColor = true; + clearDepth = true; + } + } else if (stencil) { + clearStencil(0); + clear(STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT); + if (0 < sketch.frameCount) { + clearColor = true; + clearStencil = true; + } + } else { + clear(PGL.COLOR_BUFFER_BIT); + if (0 < sketch.frameCount) { + clearColor = true; + } + } + if (fboLayerEnabled) { + clearFrontColorBuffer(); + } + } + + protected void beginRender() { + if (sketch == null) { + sketch = graphics.parent; + } + + pgeomCount = geomCount; + geomCount = 0; + + pclearColor = clearColor; + clearColor = false; + + pclearDepth = clearDepth; + clearDepth = false; + + pclearStencil = clearStencil; + clearStencil = false; + + if (fboLayerEnabledReq) { + fboLayerEnabled = true; + fboLayerEnabledReq = false; + } + + if (fboLayerEnabled) { + if (fbolayerResetReq) { + destroyFBOLayer(); + fbolayerResetReq = false; + } + if (!fboLayerCreated) { + createFBOLayer(); + } + + // Draw to the back texture bindFramebufferImpl(FRAMEBUFFER, glColorFbo.get(0)); framebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, glColorTex.get(backTex), 0); @@ -537,65 +751,130 @@ protected void beginDraw(boolean clear0) { bindFramebufferImpl(FRAMEBUFFER, glMultiFbo.get(0)); } - if (firstFrame) { + if (sketch.frameCount == 0) { // No need to draw back color buffer because we are in the first frame. - int argb = pg.backgroundColor; - float a = ((argb >> 24) & 0xff) / 255.0f; - float r = ((argb >> 16) & 0xff) / 255.0f; - float g = ((argb >> 8) & 0xff) / 255.0f; - float b = ((argb) & 0xff) / 255.0f; - clearColor(r, g, b, a); + int argb = graphics.backgroundColor; + float ba = ((argb >> 24) & 0xff) / 255.0f; + float br = ((argb >> 16) & 0xff) / 255.0f; + float bg = ((argb >> 8) & 0xff) / 255.0f; + float bb = ((argb) & 0xff) / 255.0f; + clearColor(br, bg, bb, ba); clear(COLOR_BUFFER_BIT); - } else if (!clear0) { - // Render previous back texture (now is the front) as background, - // because no background() is being used ("incremental drawing") - drawTexture(TEXTURE_2D, glColorTex.get(frontTex), - fboWidth, fboHeight, pg.width, pg.height, - 0, 0, pg.width, pg.height, - 0, 0, pg.width, pg.height); + } else if (!pclearColor || !graphics.isLooping()) { + // Render previous back texture (now is the front) as background, because no background() + // is being used ("incremental drawing") + int x = 0; + int y = 0; + if (presentMode) { + x = (int)presentX; + y = (int)presentY; + } + float scale = getPixelScale(); + drawTexture(TEXTURE_2D, glColorTex.get(frontTex), fboWidth, fboHeight, + x, y, graphics.width, graphics.height, + 0, 0, (int)(scale * graphics.width), (int)(scale * graphics.height), + 0, 0, graphics.width, graphics.height); } - - fboLayerInUse = true; - } else { - fboLayerInUse = false; - } - - if (firstFrame) { - firstFrame = false; - } - - if (!USE_FBOLAYER_BY_DEFAULT) { - // The result of this assignment is the following: if the user requested - // at some point the use of the FBO layer, but subsequently didn't - // request it again, then the rendering won't render to the FBO layer if - // not needed by the condif, since it is slower than simple onscreen - // rendering. - fboLayerRequested = false; + } else if (SINGLE_BUFFERED && sketch.frameCount == 1) { + restoreFirstFrame(); } } - protected void endDraw(boolean clear0) { - if (fboLayerInUse) { + protected void endRender(int windowColor) { + if (fboLayerEnabled) { syncBackTexture(); // Draw the contents of the back texture to the screen framebuffer. bindFramebufferImpl(FRAMEBUFFER, 0); - clearDepth(1); - clearColor(0, 0, 0, 0); - clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT); + if (presentMode) { + float wa = ((windowColor >> 24) & 0xff) / 255.0f; + float wr = ((windowColor >> 16) & 0xff) / 255.0f; + float wg = ((windowColor >> 8) & 0xff) / 255.0f; + float wb = (windowColor & 0xff) / 255.0f; + clearDepth(1); + clearColor(wr, wg, wb, wa); + clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT); + + if (showStopButton) { + if (closeButtonTex == null) { + closeButtonTex = allocateIntBuffer(1); + genTextures(1, closeButtonTex); + bindTexture(TEXTURE_2D, closeButtonTex.get(0)); + texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST); + texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST); + texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE); + texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE); + texImage2D(TEXTURE_2D, 0, RGBA, stopButtonWidth, stopButtonHeight, 0, RGBA, UNSIGNED_BYTE, null); + + int[] color = new int[closeButtonPix.length]; + PApplet.arrayCopy(closeButtonPix, color); + + + // Multiply the texture by the button color + float ba = ((stopButtonColor >> 24) & 0xFF) / 255f; + float br = ((stopButtonColor >> 16) & 0xFF) / 255f; + float bg = ((stopButtonColor >> 8) & 0xFF) / 255f; + float bb = ((stopButtonColor >> 0) & 0xFF) / 255f; + for (int i = 0; i < color.length; i++) { + int c = closeButtonPix[i]; + int a = (int)(ba * ((c >> 24) & 0xFF)); + int r = (int)(br * ((c >> 16) & 0xFF)); + int g = (int)(bg * ((c >> 8) & 0xFF)); + int b = (int)(bb * ((c >> 0) & 0xFF)); + color[i] = javaToNativeARGB((a << 24) | (r << 16) | (g << 8) | b); + } + IntBuffer buf = allocateIntBuffer(color); + copyToTexture(TEXTURE_2D, RGBA, closeButtonTex.get(0), 0, 0, stopButtonWidth, stopButtonHeight, buf); + bindTexture(TEXTURE_2D, 0); + } + drawTexture(TEXTURE_2D, closeButtonTex.get(0), stopButtonWidth, stopButtonHeight, + 0, 0, stopButtonX + stopButtonWidth, closeButtonY + stopButtonHeight, + 0, stopButtonHeight, stopButtonWidth, 0, + stopButtonX, closeButtonY, stopButtonX + stopButtonWidth, closeButtonY + stopButtonHeight); + } + } else { + clearDepth(1); + clearColor(0, 0, 0, 0); + clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT); + } // Render current back texture to screen, without blending. disable(BLEND); + int x = 0; + int y = 0; + if (presentMode) { + x = (int)presentX; + y = (int)presentY; + } + float scale = getPixelScale(); drawTexture(TEXTURE_2D, glColorTex.get(backTex), - fboWidth, fboHeight, pg.width, pg.height, - 0, 0, pg.width, pg.height, 0, 0, pg.width, pg.height); + fboWidth, fboHeight, + x, y, graphics.width, graphics.height, + 0, 0, (int)(scale * graphics.width), (int)(scale * graphics.height), + 0, 0, graphics.width, graphics.height); // Swapping front and back textures. int temp = frontTex; frontTex = backTex; backTex = temp; + + if (fboLayerDisableReq) { + fboLayerEnabled = false; + fboLayerDisableReq = false; + } + } else { + if (SINGLE_BUFFERED && sketch.frameCount == 0) { + saveFirstFrame(); + } + + if (!clearColor && 0 < sketch.frameCount || !graphics.isLooping()) { + enableFBOLayer(); + if (SINGLE_BUFFERED) { + createFBOLayer(); + } + } } } @@ -615,46 +894,49 @@ protected void endDraw(boolean clear0) { protected abstract void swapBuffers(); - protected boolean threadIsCurrent() { + public boolean threadIsCurrent() { return Thread.currentThread() == glThread; } - protected void beginGL() { } + public void setThread(Thread thread) { + glThread = thread; + } - protected void endGL() { } + protected void beginGL() { } - private boolean needFBOLayer(boolean clear0) { - // TODO: need to revise this, on windows we might not want to use FBO layer - // even with anti-aliasing enabled... - return !clear0 || fboLayerRequested || 1 < numSamples; - } + protected void endGL() { } private void createFBOLayer() { - String ext = getString(EXTENSIONS); - if (-1 < ext.indexOf("texture_non_power_of_two")) { - fboWidth = pg.width; - fboHeight = pg.height; + float scale = getPixelScale(); + + if (hasNpotTexSupport()) { + fboWidth = (int)(scale * graphics.width); + fboHeight = (int)(scale * graphics.height); } else { - fboWidth = nextPowerOfTwo(pg.width); - fboHeight = nextPowerOfTwo(pg.height); + fboWidth = nextPowerOfTwo((int)(scale * graphics.width)); + fboHeight = nextPowerOfTwo((int)(scale * graphics.height)); } - int maxs = maxSamples(); - if (-1 < ext.indexOf("_framebuffer_multisample") && 1 < maxs) { + if (hasFboMultisampleSupport()) { + int maxs = maxSamples(); numSamples = PApplet.min(reqNumSamples, maxs); } else { numSamples = 1; } boolean multisample = 1 < numSamples; - boolean packed = ext.indexOf("packed_depth_stencil") != -1; + boolean packed = hasPackedDepthStencilSupport(); int depthBits = PApplet.min(REQUESTED_DEPTH_BITS, getDepthBits()); int stencilBits = PApplet.min(REQUESTED_STENCIL_BITS, getStencilBits()); + backTex = 0; + frontTex = 1; + boolean savedFirstFrame = SINGLE_BUFFERED && sketch.frameCount == 0 && firstFrame != null; + genTextures(2, glColorTex); for (int i = 0; i < 2; i++) { bindTexture(TEXTURE_2D, glColorTex.get(i)); @@ -664,37 +946,136 @@ private void createFBOLayer() { texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE); texImage2D(TEXTURE_2D, 0, RGBA, fboWidth, fboHeight, 0, RGBA, UNSIGNED_BYTE, null); - initTexture(TEXTURE_2D, RGBA, fboWidth, fboHeight, pg.backgroundColor); + if (i == frontTex && savedFirstFrame) { + // Copy first frame to front texture (will be drawn as background in next frame) + texSubImage2D(TEXTURE_2D, 0, 0, 0, graphics.width, graphics.height, + RGBA, UNSIGNED_BYTE, firstFrame); + } else { + // Intitialize texture with background color + initTexture(TEXTURE_2D, RGBA, fboWidth, fboHeight, graphics.backgroundColor); + } } bindTexture(TEXTURE_2D, 0); - backTex = 0; - frontTex = 1; - genFramebuffers(1, glColorFbo); bindFramebufferImpl(FRAMEBUFFER, glColorFbo.get(0)); framebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, TEXTURE_2D, glColorTex.get(backTex), 0); + if (!multisample || graphics.getHint(PConstants.ENABLE_BUFFER_READING)) { + // If not multisampled, this is the only depth and stencil buffer. + // If multisampled and depth reading enabled, these are going to + // hold downsampled depth and stencil buffers. + createDepthAndStencilBuffer(false, depthBits, stencilBits, packed); + } + if (multisample) { // Creating multisampled FBO genFramebuffers(1, glMultiFbo); bindFramebufferImpl(FRAMEBUFFER, glMultiFbo.get(0)); // color render buffer... - genRenderbuffers(1, glColorBuf); - bindRenderbuffer(RENDERBUFFER, glColorBuf.get(0)); + genRenderbuffers(1, glMultiColor); + bindRenderbuffer(RENDERBUFFER, glMultiColor.get(0)); renderbufferStorageMultisample(RENDERBUFFER, numSamples, RGBA8, fboWidth, fboHeight); framebufferRenderbuffer(FRAMEBUFFER, COLOR_ATTACHMENT0, - RENDERBUFFER, glColorBuf.get(0)); + RENDERBUFFER, glMultiColor.get(0)); + + // Creating multisampled depth and stencil buffers + createDepthAndStencilBuffer(true, depthBits, stencilBits, packed); + } + + validateFramebuffer(); + + // Clear all buffers. + clearDepth(1); + clearStencil(0); + int argb = graphics.backgroundColor; + float ba = ((argb >> 24) & 0xff) / 255.0f; + float br = ((argb >> 16) & 0xff) / 255.0f; + float bg = ((argb >> 8) & 0xff) / 255.0f; + float bb = ((argb) & 0xff) / 255.0f; + clearColor(br, bg, bb, ba); + clear(DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT); + + bindFramebufferImpl(FRAMEBUFFER, 0); + initFBOLayer(); + + fboLayerCreated = true; + } + + protected abstract void initFBOLayer(); + + + protected void saveFirstFrame() { + firstFrame = allocateDirectIntBuffer(graphics.width * graphics.height); + if (hasReadBuffer()) readBuffer(BACK); + readPixelsImpl(0, 0, graphics.width, graphics.height, RGBA, UNSIGNED_BYTE, firstFrame); + } + + + protected void restoreFirstFrame() { + if (firstFrame == null) return; + + IntBuffer tex = allocateIntBuffer(1); + genTextures(1, tex); + + int w, h; + float scale = getPixelScale(); + if (hasNpotTexSupport()) { + w = (int)(scale * graphics.width); + h = (int)(scale * graphics.height); + } else { + w = nextPowerOfTwo((int)(scale * graphics.width)); + h = nextPowerOfTwo((int)(scale * graphics.height)); } + bindTexture(TEXTURE_2D, tex.get(0)); + texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, NEAREST); + texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, NEAREST); + texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE); + texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE); + texImage2D(TEXTURE_2D, 0, RGBA, w, h, 0, RGBA, UNSIGNED_BYTE, null); + texSubImage2D(TEXTURE_2D, 0, 0, 0, graphics.width, graphics.height, RGBA, UNSIGNED_BYTE, firstFrame); + + drawTexture(TEXTURE_2D, tex.get(0), w, h, + 0, 0, graphics.width, graphics.height, + 0, 0, (int)(scale * graphics.width), (int)(scale * graphics.height), + 0, 0, graphics.width, graphics.height); + + deleteTextures(1, tex); + firstFrame.clear(); + firstFrame = null; + } + + protected void destroyFBOLayer() { + if (threadIsCurrent() && fboLayerCreated) { + deleteFramebuffers(1, glColorFbo); + deleteTextures(2, glColorTex); + deleteRenderbuffers(1, glDepthStencil); + deleteRenderbuffers(1, glDepth); + deleteRenderbuffers(1, glStencil); + + deleteFramebuffers(1, glMultiFbo); + deleteRenderbuffers(1, glMultiColor); + deleteRenderbuffers(1, glMultiDepthStencil); + deleteRenderbuffers(1, glMultiDepth); + deleteRenderbuffers(1, glMultiStencil); + } + fboLayerCreated = false; + } + + + private void createDepthAndStencilBuffer(boolean multisample, int depthBits, + int stencilBits, boolean packed) { // Creating depth and stencil buffers if (packed && depthBits == 24 && stencilBits == 8) { // packed depth+stencil buffer - genRenderbuffers(1, glDepthStencil); - bindRenderbuffer(RENDERBUFFER, glDepthStencil.get(0)); + IntBuffer depthStencilBuf = + multisample ? glMultiDepthStencil : glDepthStencil; + genRenderbuffers(1, depthStencilBuf); + bindRenderbuffer(RENDERBUFFER, depthStencilBuf.get(0)); if (multisample) { renderbufferStorageMultisample(RENDERBUFFER, numSamples, DEPTH24_STENCIL8, fboWidth, fboHeight); @@ -703,9 +1084,9 @@ private void createFBOLayer() { fboWidth, fboHeight); } framebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, RENDERBUFFER, - glDepthStencil.get(0)); + depthStencilBuf.get(0)); framebufferRenderbuffer(FRAMEBUFFER, STENCIL_ATTACHMENT, RENDERBUFFER, - glDepthStencil.get(0)); + depthStencilBuf.get(0)); } else { // separate depth and stencil buffers if (0 < depthBits) { @@ -718,8 +1099,9 @@ private void createFBOLayer() { depthComponent = DEPTH_COMPONENT16; } - genRenderbuffers(1, glDepth); - bindRenderbuffer(RENDERBUFFER, glDepth.get(0)); + IntBuffer depthBuf = multisample ? glMultiDepth : glDepth; + genRenderbuffers(1, depthBuf); + bindRenderbuffer(RENDERBUFFER, depthBuf.get(0)); if (multisample) { renderbufferStorageMultisample(RENDERBUFFER, numSamples, depthComponent, fboWidth, fboHeight); @@ -728,7 +1110,7 @@ private void createFBOLayer() { fboWidth, fboHeight); } framebufferRenderbuffer(FRAMEBUFFER, DEPTH_ATTACHMENT, - RENDERBUFFER, glDepth.get(0)); + RENDERBUFFER, depthBuf.get(0)); } if (0 < stencilBits) { @@ -741,8 +1123,9 @@ private void createFBOLayer() { stencilIndex = STENCIL_INDEX1; } - genRenderbuffers(1, glStencil); - bindRenderbuffer(RENDERBUFFER, glStencil.get(0)); + IntBuffer stencilBuf = multisample ? glMultiStencil : glStencil; + genRenderbuffers(1, stencilBuf); + bindRenderbuffer(RENDERBUFFER, stencilBuf.get(0)); if (multisample) { renderbufferStorageMultisample(RENDERBUFFER, numSamples, stencilIndex, fboWidth, fboHeight); @@ -751,26 +1134,14 @@ private void createFBOLayer() { fboWidth, fboHeight); } framebufferRenderbuffer(FRAMEBUFFER, STENCIL_ATTACHMENT, - RENDERBUFFER, glStencil.get(0)); + RENDERBUFFER, stencilBuf.get(0)); } } + } - validateFramebuffer(); - // Clear all buffers. - clearDepth(1); - clearStencil(0); - int argb = pg.backgroundColor; - float a = ((argb >> 24) & 0xff) / 255.0f; - float r = ((argb >> 16) & 0xff) / 255.0f; - float g = ((argb >> 8) & 0xff) / 255.0f; - float b = ((argb) & 0xff) / 255.0f; - clearColor(r, g, b, a); - clear(DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT | COLOR_BUFFER_BIT); + protected void clearFrontColorBuffer() { - bindFramebufferImpl(FRAMEBUFFER, 0); - - fboLayerCreated = true; } @@ -847,7 +1218,7 @@ protected void initTexture(int target, int format, int width, int height) { protected void initTexture(int target, int format, int width, int height, - int initColor) { + int initColor) { int[] glcolor = new int[16 * 16]; Arrays.fill(glcolor, javaToNativeARGB(initColor)); IntBuffer texels = allocateDirectIntBuffer(16 * 16); @@ -863,6 +1234,12 @@ protected void initTexture(int target, int format, int width, int height, } + protected void copyToTexture(int target, int format, int id, int x, int y, + int w, int h, int[] buffer) { + copyToTexture(target, format, id, x, y, w, h, IntBuffer.wrap(buffer)); + + } + protected void copyToTexture(int target, int format, int id, int x, int y, int w, int h, IntBuffer buffer) { activeTexture(TEXTURE0); @@ -885,24 +1262,42 @@ protected void copyToTexture(int target, int format, int id, int x, int y, */ public void drawTexture(int target, int id, int width, int height, int X0, int Y0, int X1, int Y1) { - drawTexture(target, id, width, height, width, height, - X0, Y0, X1, Y1, X0, Y0, X1, Y1); + // If a texture is drawing on a viewport of the same size as its resolution, + // the pixel factor is 1:1, so we override the surface's pixel factor. + drawTexture(target, id, width, height, + 0, 0, width, height, 1, + X0, Y0, X1, Y1, + X0, Y0, X1, Y1); } /** * Not an approved function, this will change or be removed in the future. */ - public void drawTexture(int target, int id, - int texW, int texH, int scrW, int scrH, + public void drawTexture(int target, int id,int texW, int texH, + int viewX, int viewY, int viewW, int viewH, + int texX0, int texY0, int texX1, int texY1, + int scrX0, int scrY0, int scrX1, int scrY1) { + int viewF = (int)getPixelScale(); + drawTexture(target, id, texW, texH, + viewX, viewY, viewW, viewH, viewF, + texX0, texY0, texX1, texY1, + scrX0, scrY0, scrX1, scrY1); + } + + + public void drawTexture(int target, int id,int texW, int texH, + int viewX, int viewY, int viewW, int viewH, int viewF, int texX0, int texY0, int texX1, int texY1, int scrX0, int scrY0, int scrX1, int scrY1) { if (target == TEXTURE_2D) { - drawTexture2D(id, texW, texH, scrW, scrH, + drawTexture2D(id, texW, texH, + viewX, viewY, viewW, viewH, viewF, texX0, texY0, texX1, texY1, scrX0, scrY0, scrX1, scrY1); } else if (target == TEXTURE_RECTANGLE) { - drawTextureRect(id, texW, texH, scrW, scrH, + drawTextureRect(id, texW, texH, + viewX, viewY, viewW, viewH, viewF, texX0, texY0, texX1, texY1, scrX0, scrY0, scrX1, scrY1); } @@ -910,11 +1305,13 @@ public void drawTexture(int target, int id, protected PGL initTex2DShader() { - PGL ppgl = primaryPGL ? this : pg.getPrimaryPGL(); + PGL ppgl = primaryPGL ? this : graphics.getPrimaryPGL(); if (!ppgl.loadedTex2DShader || ppgl.tex2DShaderContext != ppgl.glContext) { - String vertSource = PApplet.join(texVertShaderSource, "\n"); - String fragSource = PApplet.join(tex2DFragShaderSource, "\n"); + String[] preprocVertSrc = preprocessVertexSource(texVertShaderSource, getGLSLVersion()); + String vertSource = PApplet.join(preprocVertSrc, "\n"); + String[] preprocFragSrc = preprocessFragmentSource(tex2DFragShaderSource, getGLSLVersion()); + String fragSource = PApplet.join(preprocFragSrc, "\n"); ppgl.tex2DVertShader = createShader(VERTEX_SHADER, vertSource); ppgl.tex2DFragShader = createShader(FRAGMENT_SHADER, fragSource); if (0 < ppgl.tex2DVertShader && 0 < ppgl.tex2DFragShader) { @@ -942,7 +1339,8 @@ protected PGL initTex2DShader() { } - protected void drawTexture2D(int id, int texW, int texH, int scrW, int scrH, + protected void drawTexture2D(int id, int texW, int texH, + int viewX, int viewY, int viewW, int viewH, int viewF, int texX0, int texY0, int texX1, int texY1, int scrX0, int scrY0, int scrX1, int scrY1) { PGL ppgl = initTex2DShader(); @@ -962,7 +1360,7 @@ protected void drawTexture2D(int id, int texW, int texH, int scrW, int scrH, // Making sure that the viewport matches the provided screen dimensions viewBuffer.rewind(); getIntegerv(VIEWPORT, viewBuffer); - viewport(0, 0, scrW, scrH); + viewportImpl(viewF * viewX, viewF * viewY, viewF * viewW, viewF * viewH); useProgram(ppgl.tex2DShaderProgram); @@ -972,23 +1370,23 @@ protected void drawTexture2D(int id, int texW, int texH, int scrW, int scrH, // Vertex coordinates of the textured quad are specified // in normalized screen space (-1, 1): // Corner 1 - texCoords[ 0] = 2 * (float)scrX0 / scrW - 1; - texCoords[ 1] = 2 * (float)scrY0 / scrH - 1; + texCoords[ 0] = 2 * (float)scrX0 / viewW - 1; + texCoords[ 1] = 2 * (float)scrY0 / viewH - 1; texCoords[ 2] = (float)texX0 / texW; texCoords[ 3] = (float)texY0 / texH; // Corner 2 - texCoords[ 4] = 2 * (float)scrX1 / scrW - 1; - texCoords[ 5] = 2 * (float)scrY0 / scrH - 1; + texCoords[ 4] = 2 * (float)scrX1 / viewW - 1; + texCoords[ 5] = 2 * (float)scrY0 / viewH - 1; texCoords[ 6] = (float)texX1 / texW; texCoords[ 7] = (float)texY0 / texH; // Corner 3 - texCoords[ 8] = 2 * (float)scrX0 / scrW - 1; - texCoords[ 9] = 2 * (float)scrY1 / scrH - 1; + texCoords[ 8] = 2 * (float)scrX0 / viewW - 1; + texCoords[ 9] = 2 * (float)scrY1 / viewH - 1; texCoords[10] = (float)texX0 / texW; texCoords[11] = (float)texY1 / texH; // Corner 4 - texCoords[12] = 2 * (float)scrX1 / scrW - 1; - texCoords[13] = 2 * (float)scrY1 / scrH - 1; + texCoords[12] = 2 * (float)scrX1 / viewW - 1; + texCoords[13] = 2 * (float)scrY1 / viewH - 1; texCoords[14] = (float)texX1 / texW; texCoords[15] = (float)texY1 / texH; @@ -1032,18 +1430,20 @@ protected void drawTexture2D(int id, int texW, int texH, int scrW, int scrH, } depthMask(depthMask); - viewport(viewBuffer.get(0), viewBuffer.get(1), - viewBuffer.get(2), viewBuffer.get(3)); + viewportImpl(viewBuffer.get(0), viewBuffer.get(1), + viewBuffer.get(2), viewBuffer.get(3)); } } protected PGL initTexRectShader() { - PGL ppgl = primaryPGL ? this : pg.getPrimaryPGL(); + PGL ppgl = primaryPGL ? this : graphics.getPrimaryPGL(); if (!ppgl.loadedTexRectShader || ppgl.texRectShaderContext != ppgl.glContext) { - String vertSource = PApplet.join(texVertShaderSource, "\n"); - String fragSource = PApplet.join(texRectFragShaderSource, "\n"); + String[] preprocVertSrc = preprocessVertexSource(texVertShaderSource, getGLSLVersion()); + String vertSource = PApplet.join(preprocVertSrc, "\n"); + String[] preprocFragSrc = preprocessFragmentSource(texRectFragShaderSource, getGLSLVersion()); + String fragSource = PApplet.join(preprocFragSrc, "\n"); ppgl.texRectVertShader = createShader(VERTEX_SHADER, vertSource); ppgl.texRectFragShader = createShader(FRAGMENT_SHADER, fragSource); if (0 < ppgl.texRectVertShader && 0 < ppgl.texRectFragShader) { @@ -1068,7 +1468,8 @@ protected PGL initTexRectShader() { } - protected void drawTextureRect(int id, int texW, int texH, int scrW, int scrH, + protected void drawTextureRect(int id, int texW, int texH, + int viewX, int viewY, int viewW, int viewH, int viewF, int texX0, int texY0, int texX1, int texY1, int scrX0, int scrY0, int scrX1, int scrY1) { PGL ppgl = initTexRectShader(); @@ -1092,7 +1493,7 @@ protected void drawTextureRect(int id, int texW, int texH, int scrW, int scrH, // Making sure that the viewport matches the provided screen dimensions viewBuffer.rewind(); getIntegerv(VIEWPORT, viewBuffer); - viewport(0, 0, scrW, scrH); + viewportImpl(viewF * viewX, viewF * viewY, viewF * viewW, viewF * viewH); useProgram(ppgl.texRectShaderProgram); @@ -1102,23 +1503,23 @@ protected void drawTextureRect(int id, int texW, int texH, int scrW, int scrH, // Vertex coordinates of the textured quad are specified // in normalized screen space (-1, 1): // Corner 1 - texCoords[ 0] = 2 * (float)scrX0 / scrW - 1; - texCoords[ 1] = 2 * (float)scrY0 / scrH - 1; + texCoords[ 0] = 2 * (float)scrX0 / viewW - 1; + texCoords[ 1] = 2 * (float)scrY0 / viewH - 1; texCoords[ 2] = texX0; texCoords[ 3] = texY0; // Corner 2 - texCoords[ 4] = 2 * (float)scrX1 / scrW - 1; - texCoords[ 5] = 2 * (float)scrY0 / scrH - 1; + texCoords[ 4] = 2 * (float)scrX1 / viewW - 1; + texCoords[ 5] = 2 * (float)scrY0 / viewH - 1; texCoords[ 6] = texX1; texCoords[ 7] = texY0; // Corner 3 - texCoords[ 8] = 2 * (float)scrX0 / scrW - 1; - texCoords[ 9] = 2 * (float)scrY1 / scrH - 1; + texCoords[ 8] = 2 * (float)scrX0 / viewW - 1; + texCoords[ 9] = 2 * (float)scrY1 / viewH - 1; texCoords[10] = texX0; texCoords[11] = texY1; // Corner 4 - texCoords[12] = 2 * (float)scrX1 / scrW - 1; - texCoords[13] = 2 * (float)scrY1 / scrH - 1; + texCoords[12] = 2 * (float)scrX1 / viewW - 1; + texCoords[13] = 2 * (float)scrY1 / viewH - 1; texCoords[14] = texX1; texCoords[15] = texY1; @@ -1162,8 +1563,8 @@ protected void drawTextureRect(int id, int texW, int texH, int scrW, int scrH, } depthMask(depthMask); - viewport(viewBuffer.get(0), viewBuffer.get(1), - viewBuffer.get(2), viewBuffer.get(3)); + viewportImpl(viewBuffer.get(0), viewBuffer.get(1), + viewBuffer.get(2), viewBuffer.get(3)); } } @@ -1173,7 +1574,7 @@ protected int getColorValue(int scrX, int scrY) { colorBuffer = IntBuffer.allocate(1); } colorBuffer.rewind(); - readPixels(scrX, pg.height - scrY - 1, 1, 1, RGBA, UNSIGNED_BYTE, + readPixels(scrX, graphics.height - scrY - 1, 1, 1, RGBA, UNSIGNED_BYTE, colorBuffer); return colorBuffer.get(); } @@ -1184,7 +1585,7 @@ protected float getDepthValue(int scrX, int scrY) { depthBuffer = FloatBuffer.allocate(1); } depthBuffer.rewind(); - readPixels(scrX, pg.height - scrY - 1, 1, 1, DEPTH_COMPONENT, FLOAT, + readPixels(scrX, graphics.height - scrY - 1, 1, 1, DEPTH_COMPONENT, FLOAT, depthBuffer); return depthBuffer.get(0); } @@ -1194,18 +1595,22 @@ protected byte getStencilValue(int scrX, int scrY) { if (stencilBuffer == null) { stencilBuffer = ByteBuffer.allocate(1); } - readPixels(scrX, pg.height - scrY - 1, 1, 1, STENCIL_INDEX, + stencilBuffer.rewind(); + readPixels(scrX, graphics.height - scrY - 1, 1, 1, STENCIL_INDEX, UNSIGNED_BYTE, stencilBuffer); return stencilBuffer.get(0); } + protected static boolean isPowerOfTwo(int val) { + return (val & (val - 1)) == 0; + } + + // bit shifting this might be more efficient protected static int nextPowerOfTwo(int val) { int ret = 1; - while (ret < val) { - ret <<= 1; - } + while (ret < val) ret <<= 1; return ret; } @@ -1216,12 +1621,10 @@ protected static int nextPowerOfTwo(int val) { */ protected static int nativeToJavaARGB(int color) { if (BIG_ENDIAN) { // RGBA to ARGB - return (color >>> 8) | ((color << 24) & 0xFF000000); - // equivalent to - // ((color >> 8) & 0x00FFFFFF) | ((color << 24) & 0xFF000000) + return (color >>> 8) | (color << 24); } else { // ABGR to ARGB - return ((color & 0xFF) << 16) | ((color & 0xFF0000) >> 16) | - (color & 0xFF00FF00); + int rb = color & 0x00FF00FF; + return (color & 0xFF00FF00) | (rb << 16) | (rb >> 16); } } @@ -1240,13 +1643,13 @@ protected static void nativeToJavaARGB(int[] pixels, int width, int height) { int pixy = pixels[yindex]; int pixi = pixels[index]; if (BIG_ENDIAN) { // RGBA to ARGB - pixels[index] = (pixy >>> 8) | ((pixy << 24) & 0xFF000000); - pixels[yindex] = (pixi >>> 8) | ((pixi << 24) & 0xFF000000); + pixels[index] = (pixy >>> 8) | (pixy << 24); + pixels[yindex] = (pixi >>> 8) | (pixi << 24); } else { // ABGR to ARGB - pixels[index] = ((pixy & 0xFF) << 16) | ((pixy & 0xFF0000) >> 16) | - (pixy & 0xFF00FF00); - pixels[yindex] = ((pixi & 0xFF) << 16) | ((pixi & 0xFF0000) >> 16) | - (pixi & 0xFF00FF00); + int rbi = pixi & 0x00FF00FF; + int rby = pixy & 0x00FF00FF; + pixels[index] = (pixy & 0xFF00FF00) | (rby << 16) | (rby >> 16); + pixels[yindex] = (pixi & 0xFF00FF00) | (rbi << 16) | (rbi >> 16); } index++; yindex++; @@ -1259,10 +1662,10 @@ protected static void nativeToJavaARGB(int[] pixels, int width, int height) { for (int x = 0; x < width; x++) { int pixi = pixels[index]; if (BIG_ENDIAN) { // RGBA to ARGB - pixels[index] = (pixi >>> 8) | ((pixi << 24) & 0xFF000000); + pixels[index] = (pixi >>> 8) | (pixi << 24); } else { // ABGR to ARGB - pixels[index] = ((pixi & 0xFF) << 16) | ((pixi & 0xFF0000) >> 16) | - (pixi & 0xFF00FF00); + int rbi = pixi & 0x00FF00FF; + pixels[index] = (pixi & 0xFF00FF00) | (rbi << 16) | (rbi >> 16); } index++; } @@ -1279,8 +1682,9 @@ protected static int nativeToJavaRGB(int color) { if (BIG_ENDIAN) { // RGBA to ARGB return (color >>> 8) | 0xFF000000; } else { // ABGR to ARGB - return ((color & 0xFF) << 16) | ((color & 0xFF0000) >> 16) | - (color & 0xFF00FF00) | 0xFF000000; + int rb = color & 0x00FF00FF; + return 0xFF000000 | (rb << 16) | + (color & 0x0000FF00) | (rb >> 16); } } @@ -1303,10 +1707,12 @@ protected static void nativeToJavaRGB(int[] pixels, int width, int height) { pixels[index] = (pixy >>> 8) | 0xFF000000; pixels[yindex] = (pixi >>> 8) | 0xFF000000; } else { // ABGR to ARGB - pixels[index] = ((pixy & 0xFF) << 16) | ((pixy & 0xFF0000) >> 16) | - (pixy & 0xFF00FF00) | 0xFF000000; - pixels[yindex] = ((pixi & 0xFF) << 16) | ((pixi & 0xFF0000) >> 16) | - (pixi & 0xFF00FF00) | 0xFF000000; + int rbi = pixi & 0x00FF00FF; + int rby = pixy & 0x00FF00FF; + pixels[index] = 0xFF000000 | (rby << 16) | + (pixy & 0x0000FF00) | (rby >> 16); + pixels[yindex] = 0xFF000000 | (rbi << 16) | + (pixi & 0x0000FF00) | (rbi >> 16); } index++; yindex++; @@ -1321,8 +1727,9 @@ protected static void nativeToJavaRGB(int[] pixels, int width, int height) { if (BIG_ENDIAN) { // RGBA to ARGB pixels[index] = (pixi >>> 8) | 0xFF000000; } else { // ABGR to ARGB - pixels[index] = ((pixi & 0xFF) << 16) | ((pixi & 0xFF0000) >> 16) | - (pixi & 0xFF00FF00) | 0xFF000000; + int rbi = pixi & 0x00FF00FF; + pixels[index] = 0xFF000000 | (rbi << 16) | + (pixi & 0x000FF00) | (rbi >> 16); } index++; } @@ -1336,10 +1743,10 @@ protected static void nativeToJavaRGB(int[] pixels, int width, int height) { */ protected static int javaToNativeARGB(int color) { if (BIG_ENDIAN) { // ARGB to RGBA - return ((color >> 24) & 0xFF) | ((color << 8) & 0xFFFFFF00); + return (color >>> 24) | (color << 8); } else { // ARGB to ABGR - return (color & 0xFF000000) | ((color << 16) & 0xFF0000) | - (color & 0xFF00) | ((color >> 16) & 0xFF); + int rb = color & 0x00FF00FF; + return (color & 0xFF00FF00) | (rb << 16) | (rb >> 16); } } @@ -1358,13 +1765,13 @@ protected static void javaToNativeARGB(int[] pixels, int width, int height) { int pixy = pixels[yindex]; int pixi = pixels[index]; if (BIG_ENDIAN) { // ARGB to RGBA - pixels[index] = ((pixy >> 24) & 0xFF) | ((pixy << 8) & 0xFFFFFF00); - pixels[yindex] = ((pixi >> 24) & 0xFF) | ((pixi << 8) & 0xFFFFFF00); + pixels[index] = (pixy >>> 24) | (pixy << 8); + pixels[yindex] = (pixi >>> 24) | (pixi << 8); } else { // ARGB to ABGR - pixels[index] = (pixy & 0xFF000000) | ((pixy << 16) & 0xFF0000) | - (pixy & 0xFF00) | ((pixy >> 16) & 0xFF); - pixels[yindex] = (pixi & 0xFF000000) | ((pixi << 16) & 0xFF0000) | - (pixi & 0xFF00) | ((pixi >> 16) & 0xFF); + int rbi = pixi & 0x00FF00FF; + int rby = pixy & 0x00FF00FF; + pixels[index] = (pixy & 0xFF00FF00) | (rby << 16) | (rby >> 16); + pixels[yindex] = (pixi & 0xFF00FF00) | (rbi << 16) | (rbi >> 16); } index++; yindex++; @@ -1377,10 +1784,10 @@ protected static void javaToNativeARGB(int[] pixels, int width, int height) { for (int x = 0; x < width; x++) { int pixi = pixels[index]; if (BIG_ENDIAN) { // ARGB to RGBA - pixels[index] = ((pixi >> 24) & 0xFF) | ((pixi << 8) & 0xFFFFFF00); + pixels[index] = (pixi >>> 24) | (pixi << 8); } else { // ARGB to ABGR - pixels[index] = (pixi & 0xFF000000) | ((pixi << 16) & 0xFF0000) | - (pixi & 0xFF00) | ((pixi >> 16) & 0xFF); + int rbi = pixi & 0x00FF00FF; + pixels[index] = (pixi & 0xFF00FF00) | (rbi << 16) | (rbi >> 16); } index++; } @@ -1394,10 +1801,10 @@ protected static void javaToNativeARGB(int[] pixels, int width, int height) { */ protected static int javaToNativeRGB(int color) { if (BIG_ENDIAN) { // ARGB to RGB - return 0xFF | ((color << 8) & 0xFFFFFF00); + return 0xFF | (color << 8); } else { // ARGB to BGR - return 0xFF000000 | ((color << 16) & 0xFF0000) | - (color & 0xFF00) | ((color >> 16) & 0xFF); + int rb = color & 0x00FF00FF; + return 0xFF000000 | (rb << 16) | (color & 0x0000FF00) | (rb >> 16); } } @@ -1417,13 +1824,15 @@ protected static void javaToNativeRGB(int[] pixels, int width, int height) { int pixy = pixels[yindex]; int pixi = pixels[index]; if (BIG_ENDIAN) { // ARGB to RGB - pixels[index] = 0xFF | ((pixy << 8) & 0xFFFFFF00); - pixels[yindex] = 0xFF | ((pixi << 8) & 0xFFFFFF00); + pixels[index] = 0xFF | (pixy << 8); + pixels[yindex] = 0xFF | (pixi << 8); } else { // ARGB to BGR - pixels[index] = 0xFF000000 | ((pixy << 16) & 0xFF0000) | - (pixy & 0xFF00) | ((pixy >> 16) & 0xFF); - pixels[yindex] = 0xFF000000 | ((pixi << 16) & 0xFF0000) | - (pixi & 0xFF00) | ((pixi >> 16) & 0xFF); + int rbi = pixi & 0x00FF00FF; + int rby = pixy & 0x00FF00FF; + pixels[index] = 0xFF000000 | (rby << 16) | + (pixy & 0x0000FF00) | (rby >> 16); + pixels[yindex] = 0xFF000000 | (rbi << 16) | + (pixi & 0x0000FF00) | (rbi >> 16); } index++; yindex++; @@ -1436,10 +1845,11 @@ protected static void javaToNativeRGB(int[] pixels, int width, int height) { for (int x = 0; x < width; x++) { int pixi = pixels[index]; if (BIG_ENDIAN) { // ARGB to RGB - pixels[index] = 0xFF | ((pixi << 8) & 0xFFFFFF00); + pixels[index] = 0xFF | (pixi << 8); } else { // ARGB to BGR - pixels[index] = 0xFF000000 | ((pixi << 16) & 0xFF0000) | - (pixi & 0xFF00) | ((pixi >> 16) & 0xFF); + int rbi = pixi & 0x00FF00FF; + pixels[index] = 0xFF000000 | (rbi << 16) | + (pixi & 0x0000FF00) | (rbi >> 16); } index++; } @@ -1458,13 +1868,16 @@ protected static int qualityToSamples(int quality) { } + abstract protected int getGLSLVersion(); + + protected String[] loadVertexShader(String filename) { - return pg.parent.loadStrings(filename); + return sketch.loadStrings(filename); } protected String[] loadFragmentShader(String filename) { - return pg.parent.loadStrings(filename); + return sketch.loadStrings(filename); } @@ -1508,43 +1921,119 @@ protected String[] loadVertexShader(URL url, int version) { } - protected static String[] convertFragmentSource(String[] fragSrc0, - int version0, int version1) { - if (version0 == 120 && version1 == 150) { - String[] fragSrc = new String[fragSrc0.length + 2]; - fragSrc[0] = "#version 150"; - fragSrc[1] = "out vec4 fragColor;"; - for (int i = 0; i < fragSrc0.length; i++) { - String line = fragSrc0[i]; - line = line.replace("varying", "in"); - line = line.replace("attribute", "in"); - line = line.replace("gl_FragColor", "fragColor"); - line = line.replace("texture", "texMap"); - line = line.replace("texMap2D(", "texture("); - line = line.replace("texMap2DRect(", "texture("); - fragSrc[i + 2] = line; - } - return fragSrc; + protected static String[] preprocessFragmentSource(String[] fragSrc0, + int version) { + if (containsVersionDirective(fragSrc0)) { + // The user knows what she or he is doing + return fragSrc0; } - return fragSrc0; + + String[] fragSrc; + + if (version < 130) { + Pattern[] search = { }; + String[] replace = { }; + int offset = 1; + + fragSrc = preprocessShaderSource(fragSrc0, search, replace, offset); + fragSrc[0] = "#version " + version; + } else { + // We need to replace 'texture' uniform by 'texMap' uniform and + // 'textureXXX()' functions by 'texture()' functions. Order of these + // replacements is important to prevent collisions between these two. + Pattern[] search = new Pattern[] { + Pattern.compile(String.format(GLSL_ID_REGEX, "varying|attribute")), + Pattern.compile(String.format(GLSL_ID_REGEX, "texture")), + Pattern.compile(String.format(GLSL_FN_REGEX, "textureRect|texture2D|texture3D|textureCube")), + Pattern.compile(String.format(GLSL_ID_REGEX, "gl_FragColor")) + }; + String[] replace = new String[] { + "in", "texMap", "texture", "_fragColor" + }; + int offset = 2; + + fragSrc = preprocessShaderSource(fragSrc0, search, replace, offset); + fragSrc[0] = "#version " + version; + fragSrc[1] = "out vec4 _fragColor;"; + } + + return fragSrc; } + protected static String[] preprocessVertexSource(String[] vertSrc0, + int version) { + if (containsVersionDirective(vertSrc0)) { + // The user knows what she or he is doing + return vertSrc0; + } + String[] vertSrc; - protected static String[] convertVertexSource(String[] vertSrc0, - int version0, int version1) { - if (version0 == 120 && version1 == 150) { - String[] vertSrc = new String[vertSrc0.length + 1]; - vertSrc[0] = "#version 150"; - for (int i = 0; i < vertSrc0.length; i++) { - String line = vertSrc0[i]; - line = line.replace("attribute", "in"); - line = line.replace("varying", "out"); - vertSrc[i + 1] = line; + if (version < 130) { + Pattern[] search = { }; + String[] replace = { }; + int offset = 1; + + vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); + vertSrc[0] = "#version " + version; + } else { + // We need to replace 'texture' uniform by 'texMap' uniform and + // 'textureXXX()' functions by 'texture()' functions. Order of these + // replacements is important to prevent collisions between these two. + Pattern[] search = new Pattern[] { + Pattern.compile(String.format(GLSL_ID_REGEX, "varying")), + Pattern.compile(String.format(GLSL_ID_REGEX, "attribute")), + Pattern.compile(String.format(GLSL_ID_REGEX, "texture")), + Pattern.compile(String.format(GLSL_FN_REGEX, "textureRect|texture2D|texture3D|textureCube")) + }; + String[] replace = new String[] { + "out", "in", "texMap", "texture", + }; + int offset = 1; + + vertSrc = preprocessShaderSource(vertSrc0, search, replace, offset); + vertSrc[0] = "#version " + version; + } + + return vertSrc; + } + + + protected static final String GLSL_ID_REGEX = "(?= 0) { + line = line.substring(0, versionIndex); } - return vertSrc; + for (int j = 0; j < search.length; j++) { + line = search[j].matcher(line).replaceAll(replace[j]); + } + src[i+offset] = line; } - return vertSrc0; + return src; + } + + protected static boolean containsVersionDirective(String[] shSrc) { + for (int i = 0; i < shSrc.length; i++) { + String line = shSrc[i]; + int versionIndex = line.indexOf("#version"); + if (versionIndex >= 0) { + int commentIndex = line.indexOf("//"); + if (commentIndex < 0 || versionIndex < commentIndex) { + return true; + } + } + } + return false; } protected int createShader(int shaderType, String source) { @@ -1620,9 +2109,19 @@ protected boolean validateFramebuffer() { return false; } + protected boolean isES() { + return getString(VERSION).trim().toLowerCase().contains("opengl es"); + } protected int[] getGLVersion() { - String version = getString(VERSION).trim(); + String version = getString(VERSION).trim().toLowerCase(); + + String ES = "opengl es"; + int esPosition = version.indexOf(ES); + if (esPosition >= 0) { + version = version.substring(esPosition + ES.length()).trim(); + } + int[] res = {0, 0, 0}; String[] parts = version.split(" "); for (int i = 0; i < parts.length; i++) { @@ -1735,6 +2234,42 @@ protected boolean hasAnisoSamplingSupport() { } + protected boolean hasSynchronization() { + int[] version = getGLVersion(); + if (isES()) { + return version[0] >= 3; + } + return (version[0] > 3) || (version[0] == 3 && version[1] >= 2); + } + + + protected boolean hasPBOs() { + int[] version = getGLVersion(); + if (isES()) { + return version[0] >= 3; + } + return (version[0] > 2) || (version[0] == 2 && version[1] >= 1); + } + + + protected boolean hasReadBuffer() { + int[] version = getGLVersion(); + if (isES()) { + return version[0] >= 3; + } + return version[0] >= 2; + } + + + protected boolean hasDrawBuffer() { + int[] version = getGLVersion(); + if (isES()) { + return version[0] >= 3; + } + return version[0] >= 2; + } + + protected int maxSamples() { intBuffer.rewind(); getIntegerv(MAX_SAMPLES, intBuffer); @@ -1766,7 +2301,10 @@ protected static ByteBuffer allocateByteBuffer(int size) { protected static ByteBuffer allocateByteBuffer(byte[] arr) { if (USE_DIRECT_BUFFERS) { - return allocateDirectByteBuffer(arr.length); + ByteBuffer buf = allocateDirectByteBuffer(arr.length); + buf.put(arr); + buf.position(0); + return buf; } else { return ByteBuffer.wrap(arr); } @@ -1855,7 +2393,10 @@ protected static ShortBuffer allocateShortBuffer(int size) { protected static ShortBuffer allocateShortBuffer(short[] arr) { if (USE_DIRECT_BUFFERS) { - return allocateDirectShortBuffer(arr.length); + ShortBuffer buf = allocateDirectShortBuffer(arr.length); + buf.put(arr); + buf.position(0); + return buf; } else { return ShortBuffer.wrap(arr); } @@ -1944,7 +2485,10 @@ protected static IntBuffer allocateIntBuffer(int size) { protected static IntBuffer allocateIntBuffer(int[] arr) { if (USE_DIRECT_BUFFERS) { - return allocateDirectIntBuffer(arr.length); + IntBuffer buf = allocateDirectIntBuffer(arr.length); + buf.put(arr); + buf.position(0); + return buf; } else { return IntBuffer.wrap(arr); } @@ -2032,7 +2576,10 @@ protected static FloatBuffer allocateFloatBuffer(int size) { protected static FloatBuffer allocateFloatBuffer(float[] arr) { if (USE_DIRECT_BUFFERS) { - return allocateDirectFloatBuffer(arr.length); + FloatBuffer buf = allocateDirectFloatBuffer(arr.length); + buf.put(arr); + buf.position(0); + return buf; } else { return FloatBuffer.wrap(arr); } @@ -2104,25 +2651,18 @@ protected static void fillFloatBuffer(FloatBuffer buf, int i0, int i1, // TODO: the next three functions shouldn't be here... + // Uses 'Object' so that the API can be used w/ Android Typeface objects - protected int getFontAscent(Object font) { - return 0; - } + abstract protected int getFontAscent(Object font); - protected int getFontDescent(Object font) { - return 0; - } + abstract protected int getFontDescent(Object font); - protected int getTextWidth(Object font, char buffer[], int start, int stop) { - return 0; - } + abstract protected int getTextWidth(Object font, char[] buffer, int start, int stop); - protected Object getDerivedFont(Object font, float size) { - return null; - } + abstract protected Object getDerivedFont(Object font, float size); /////////////////////////////////////////////////////////// @@ -2134,12 +2674,17 @@ protected Object getDerivedFont(Object font, float size) { protected interface Tessellator { + public void setCallback(int flag); + public void setWindingRule(int rule); + public void setProperty(int property, int value); + public void beginPolygon(); + public void beginPolygon(Object data); public void endPolygon(); - public void setWindingRule(int rule); public void beginContour(); public void endContour(); public void addVertex(double[] v); + public void addVertex(double[] v, int n, Object data); } @@ -2251,6 +2796,7 @@ protected interface FontOutline { public static int TESS_WINDING_NONZERO; public static int TESS_WINDING_ODD; + public static int TESS_EDGE_FLAG; public static int GENERATE_MIPMAP_HINT; public static int FASTEST; @@ -2279,12 +2825,14 @@ protected interface FontOutline { public static int ARRAY_BUFFER; public static int ELEMENT_ARRAY_BUFFER; + public static int PIXEL_PACK_BUFFER; public static int MAX_VERTEX_ATTRIBS; public static int STATIC_DRAW; public static int DYNAMIC_DRAW; public static int STREAM_DRAW; + public static int STREAM_READ; public static int BUFFER_SIZE; public static int BUFFER_USAGE; @@ -2441,7 +2989,6 @@ protected interface FontOutline { public static int STENCIL_TEST; public static int DEPTH_TEST; public static int DEPTH_WRITEMASK; - public static int ALPHA_TEST; public static int COLOR_BUFFER_BIT; public static int DEPTH_BUFFER_BIT; @@ -2497,10 +3044,13 @@ protected interface FontOutline { public static int RENDERBUFFER_INTERNAL_FORMAT; public static int MULTISAMPLE; - public static int POINT_SMOOTH; public static int LINE_SMOOTH; public static int POLYGON_SMOOTH; + public static int SYNC_GPU_COMMANDS_COMPLETE; + public static int ALREADY_SIGNALED; + public static int CONDITION_SATISFIED; + /////////////////////////////////////////////////////////// // Special Functions @@ -2545,10 +3095,19 @@ protected interface FontOutline { ////////////////////////////////////////////////////////////////////////////// + // Synchronization + + public abstract long fenceSync(int condition, int flags); + public abstract void deleteSync(long sync); + public abstract int clientWaitSync(long sync, int flags, long timeout); + + ////////////////////////////////////////////////////////////////////////////// + // Viewport and Clipping public abstract void depthRangef(float n, float f); public abstract void viewport(int x, int y, int w, int h); + protected abstract void viewportImpl(int x, int y, int w, int h); ////////////////////////////////////////////////////////////////////////////// @@ -2559,14 +3118,37 @@ protected interface FontOutline { // to glReadPixels() should be done in readPixelsImpl(). public void readPixels(int x, int y, int width, int height, int format, int type, Buffer buffer){ - boolean pgCall = format != STENCIL_INDEX && - format != DEPTH_COMPONENT && format != DEPTH_STENCIL; - if (pgCall) pg.beginReadPixels(); + boolean multisampled = isMultisampled() || graphics.offscreenMultisample; + boolean depthReadingEnabled = graphics.getHint(PConstants.ENABLE_BUFFER_READING); + boolean depthRequested = format == STENCIL_INDEX || format == DEPTH_COMPONENT || format == DEPTH_STENCIL; + + if (multisampled && depthRequested && !depthReadingEnabled) { + PGraphics.showWarning(DEPTH_READING_NOT_ENABLED_ERROR); + return; + } + + graphics.beginReadPixels(); readPixelsImpl(x, y, width, height, format, type, buffer); - if (pgCall) pg.endReadPixels(); + graphics.endReadPixels(); + } + + public void readPixels(int x, int y, int width, int height, int format, int type, long offset){ + boolean multisampled = isMultisampled() || graphics.offscreenMultisample; + boolean depthReadingEnabled = graphics.getHint(PConstants.ENABLE_BUFFER_READING); + boolean depthRequested = format == STENCIL_INDEX || format == DEPTH_COMPONENT || format == DEPTH_STENCIL; + + if (multisampled && depthRequested && !depthReadingEnabled) { + PGraphics.showWarning(DEPTH_READING_NOT_ENABLED_ERROR); + return; + } + + graphics.beginReadPixels(); + readPixelsImpl(x, y, width, height, format, type, offset); + graphics.endReadPixels(); } protected abstract void readPixelsImpl(int x, int y, int width, int height, int format, int type, Buffer buffer); + protected abstract void readPixelsImpl(int x, int y, int width, int height, int format, int type, long offset); ////////////////////////////////////////////////////////////////////////////// @@ -2579,14 +3161,24 @@ public void readPixels(int x, int y, int width, int height, int format, int type public abstract void vertexAttrib1fv(int index, FloatBuffer values); public abstract void vertexAttrib2fv(int index, FloatBuffer values); public abstract void vertexAttrib3fv(int index, FloatBuffer values); - public abstract void vertexAttri4fv(int index, FloatBuffer values); + public abstract void vertexAttrib4fv(int index, FloatBuffer values); public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, int offset); - public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, Buffer data); public abstract void enableVertexAttribArray(int index); public abstract void disableVertexAttribArray(int index); - public abstract void drawArrays(int mode, int first, int count); - public abstract void drawElements(int mode, int count, int type, int offset); - public abstract void drawElements(int mode, int count, int type, Buffer indices); + + public void drawArrays(int mode, int first, int count) { + geomCount += count; + drawArraysImpl(mode, first, count); + } + + public abstract void drawArraysImpl(int mode, int first, int count); + + public void drawElements(int mode, int count, int type, int offset) { + geomCount += count; + drawElementsImpl(mode, count, type, offset); + } + + public abstract void drawElementsImpl(int mode, int count, int type, int offset); ////////////////////////////////////////////////////////////////////////////// @@ -2726,7 +3318,6 @@ public void bindTexture(int target, int texture) { public abstract void blendFunc(int src, int dst); public abstract void blendFuncSeparate(int srcRGB, int dstRGB, int srcAlpha, int dstAlpha); public abstract void blendColor(float red, float green, float blue, float alpha); - public abstract void alphaFunc(int func, float ref); /////////////////////////////////////////////////////////// @@ -2736,19 +3327,19 @@ public void bindTexture(int target, int texture) { public abstract void depthMask(boolean mask); public abstract void stencilMask(int mask); public abstract void stencilMaskSeparate(int face, int mask); - public abstract void clear(int buf); public abstract void clearColor(float r, float g, float b, float a); public abstract void clearDepth(float d); public abstract void clearStencil(int s); + public abstract void clear(int buf); /////////////////////////////////////////////////////////// // Framebuffers Objects public void bindFramebuffer(int target, int framebuffer) { - pg.beginBindFramebuffer(target, framebuffer); + graphics.beginBindFramebuffer(target, framebuffer); bindFramebufferImpl(target, framebuffer); - pg.endBindFramebuffer(target, framebuffer); + graphics.endBindFramebuffer(target, framebuffer); } protected abstract void bindFramebufferImpl(int target, int framebuffer); diff --git a/core/src/processing/opengl/PGLES.java b/libs/processing-core/src/main/java/processing/opengl/PGLES.java similarity index 70% rename from core/src/processing/opengl/PGLES.java rename to libs/processing-core/src/main/java/processing/opengl/PGLES.java index 0a33a60be..e31799170 100644 --- a/core/src/processing/opengl/PGLES.java +++ b/libs/processing-core/src/main/java/processing/opengl/PGLES.java @@ -1,3 +1,27 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + package processing.opengl; import java.nio.Buffer; @@ -6,17 +30,14 @@ import java.nio.IntBuffer; import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.opengles.GL10; import android.opengl.GLES20; import android.opengl.GLSurfaceView; -import android.opengl.GLSurfaceView.EGLConfigChooser; -import android.opengl.GLSurfaceView.Renderer; import android.opengl.GLU; -import processing.core.PApplet; +import android.view.SurfaceView; + import processing.opengl.tess.PGLU; import processing.opengl.tess.PGLUtessellator; import processing.opengl.tess.PGLUtessellatorCallbackAdapter; @@ -33,19 +54,14 @@ public class PGLES extends PGL { public PGLU glu; /** The current opengl context */ - public static EGLContext context; + public EGLContext context; /** The current surface view */ - public static GLSurfaceView glview; - - // ........................................................ + public GLSurfaceView glview; - // Internal objects - /** The renderer object driving the rendering loop, analogous to the - * GLEventListener in JOGL */ - protected static AndroidRenderer renderer; - protected static AndroidConfigChooser configChooser; + /** Requested major version of the OpenGL ES context */ + static public int version = 2; // ........................................................ @@ -53,11 +69,12 @@ public class PGLES extends PGL { // GLES static { + SINGLE_BUFFERED = true; + MIN_DIRECT_BUFFER_SIZE = 1; INDEX_TYPE = GLES20.GL_UNSIGNED_SHORT; - SAVE_SURFACE_TO_PIXELS_HACK = false; - MIPMAPS_ENABLED = false; + MIPMAPS_ENABLED = false; DEFAULT_IN_VERTICES = 16; DEFAULT_IN_EDGES = 32; @@ -72,17 +89,17 @@ public class PGLES extends PGL { } // Some EGL constants needed to initialize a GLES2 context. - protected static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - protected static final int EGL_OPENGL_ES2_BIT = 0x0004; + public static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + public static final int EGL_OPENGL_ES2_BIT = 0x0004; // Coverage multisampling identifiers for nVidia Tegra2 - protected static final int EGL_COVERAGE_BUFFERS_NV = 0x30E0; - protected static final int EGL_COVERAGE_SAMPLES_NV = 0x30E1; - protected static final int GL_COVERAGE_BUFFER_BIT_NV = 0x8000; + public static final int EGL_COVERAGE_BUFFERS_NV = 0x30E0; + public static final int EGL_COVERAGE_SAMPLES_NV = 0x30E1; + public static final int GL_COVERAGE_BUFFER_BIT_NV = 0x8000; - protected static boolean usingMultisampling = false; - protected static boolean usingCoverageMultisampling = false; - protected static int multisampleCount = 1; + public static boolean usingMultisampling = false; + public static boolean usingCoverageMultisampling = false; + public static int multisampleCount = 1; /////////////////////////////////////////////////////////// @@ -96,26 +113,27 @@ public PGLES(PGraphicsOpenGL pg) { @Override - public GLSurfaceView getCanvas() { + public GLSurfaceView getNative() { return glview; } @Override - protected void setFps(float fps) { } + public void queueEvent(Runnable runnable) { + if (glview != null) { + glview.queueEvent(runnable); + } + } @Override protected void initSurface(int antialias) { - glview = (GLSurfaceView)pg.parent.getSurfaceView(); + SurfaceView surf = sketch.getSurface().getSurfaceView(); + if (surf != null) { + glview = (GLSurfaceView)surf; + } reqNumSamples = qualityToSamples(antialias); - registerListeners(); - - fboLayerCreated = false; - fboLayerInUse = false; - firstFrame = true; - setFps = false; } @@ -127,412 +145,124 @@ protected void reinitSurface() { } protected void registerListeners() { } - /////////////////////////////////////////////////////////// - - // Frame rendering - - @Override - protected void getGL(PGL pgl) { - PGLES pgles = (PGLES)pgl; - this.gl = pgles.gl; + protected int getDepthBits() { + intBuffer.rewind(); + getIntegerv(DEPTH_BITS, intBuffer); + return intBuffer.get(0); } @Override - protected boolean canDraw() { - return true; + protected int getStencilBits() { + intBuffer.rewind(); + getIntegerv(STENCIL_BITS, intBuffer); + return intBuffer.get(0); } @Override - protected void requestFocus() { } + protected int getDefaultDrawBuffer() { + return fboLayerEnabled ? COLOR_ATTACHMENT0 : FRONT; + } @Override - protected void requestDraw() { - if (pg.initialized && pg.parent.canDraw()) { - glview.requestRender(); - } + protected int getDefaultReadBuffer() { + return fboLayerEnabled ? COLOR_ATTACHMENT0 : FRONT; } - @Override - protected void swapBuffers() { } - + public void init(GL10 igl) { + gl = igl; + context = ((EGL10)EGLContext.getEGL()).eglGetCurrentContext(); + glContext = context.hashCode(); + glThread = Thread.currentThread(); - /////////////////////////////////////////////////////////// + if (!hasFBOs()) { + throw new RuntimeException(PGL.MISSING_FBO_ERROR); + } + if (!hasShaders()) { + throw new RuntimeException(PGL.MISSING_GLSL_ERROR); + } + } - // Android specific classes (Renderer, ConfigChooser) + /////////////////////////////////////////////////////////// - public AndroidRenderer getRenderer() { - renderer = new AndroidRenderer(); - return renderer; - } + // Frame rendering - public AndroidContextFactory getContextFactory() { - return new AndroidContextFactory(); + @Override + protected float getPixelScale() { + return 1; } - public AndroidConfigChooser getConfigChooser(int samples) { - configChooser = new AndroidConfigChooser(5, 6, 5, 4, 16, 1, samples); - return configChooser; + @Override + protected void getGL(PGL pgl) { + PGLES pgles = (PGLES)pgl; + this.gl = pgles.gl; + setThread(pgles.glThread); } - - public AndroidConfigChooser getConfigChooser(int r, int g, int b, int a, - int d, int s, int samples) { - configChooser = new AndroidConfigChooser(r, g, b, a, d, s, samples); - return configChooser; + public void getGL(GL10 igl) { + gl = igl; + glThread = Thread.currentThread(); } - protected class AndroidRenderer implements Renderer { - public AndroidRenderer() { - } - - public void onDrawFrame(GL10 igl) { - gl = igl; - glThread = Thread.currentThread(); - pg.parent.handleDraw(); - } + @Override + protected boolean canDraw() { return true; } - public void onSurfaceChanged(GL10 igl, int iwidth, int iheight) { - gl = igl; - // Here is where we should initialize native libs... - // lib.init(iwidth, iheight); + @Override + protected void requestFocus() { } - pg.setSize(iwidth, iheight); - } - public void onSurfaceCreated(GL10 igl, EGLConfig config) { - gl = igl; - context = ((EGL10)EGLContext.getEGL()).eglGetCurrentContext(); - glContext = context.hashCode(); + @Override + protected void requestDraw() { } - if (!hasFBOs()) { - throw new RuntimeException(MISSING_FBO_ERROR); - } - if (!hasShaders()) { - throw new RuntimeException(MISSING_GLSL_ERROR); - } - } - } + @Override + protected void swapBuffers() { } - protected class AndroidContextFactory implements - GLSurfaceView.EGLContextFactory { - public EGLContext createContext(EGL10 egl, EGLDisplay display, - EGLConfig eglConfig) { - int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, - EGL10.EGL_NONE }; - EGLContext context = egl.eglCreateContext(display, eglConfig, - EGL10.EGL_NO_CONTEXT, - attrib_list); - return context; - } - public void destroyContext(EGL10 egl, EGLDisplay display, - EGLContext context) { - egl.eglDestroyContext(display, context); - } + @Override + protected int getGLSLVersion() { + return 100; } - protected class AndroidConfigChooser implements EGLConfigChooser { - // Desired size (in bits) for the rgba color, depth and stencil buffers. - public int redTarget; - public int greenTarget; - public int blueTarget; - public int alphaTarget; - public int depthTarget; - public int stencilTarget; - - // Actual rgba color, depth and stencil sizes (in bits) supported by the - // device. - public int redBits; - public int greenBits; - public int blueBits; - public int alphaBits; - public int depthBits; - public int stencilBits; - public int[] tempValue = new int[1]; - - public int numSamples; - - /* - The GLES2 extensions supported are: - GL_OES_rgb8_rgba8 GL_OES_depth24 GL_OES_vertex_half_float - GL_OES_texture_float GL_OES_texture_half_float - GL_OES_element_index_uint GL_OES_mapbuffer - GL_OES_fragment_precision_high GL_OES_compressed_ETC1_RGB8_texture - GL_OES_EGL_image GL_OES_required_internalformat GL_OES_depth_texture - GL_OES_get_program_binary GL_OES_packed_depth_stencil - GL_OES_standard_derivatives GL_OES_vertex_array_object GL_OES_egl_sync - GL_EXT_multi_draw_arrays GL_EXT_texture_format_BGRA8888 - GL_EXT_discard_framebuffer GL_EXT_shader_texture_lod - GL_IMG_shader_binary GL_IMG_texture_compression_pvrtc - GL_IMG_texture_stream2 GL_IMG_texture_npot - GL_IMG_texture_format_BGRA8888 GL_IMG_read_format - GL_IMG_program_binary GL_IMG_multisampled_render_to_texture - */ - - /* - // The attributes we want in the frame buffer configuration for Processing. - // For more details on other attributes, see: - // http://www.khronos.org/opengles/documentation/opengles1_0/html/eglChooseConfig.html - protected int[] configAttribsGL_MSAA = { - EGL10.EGL_RED_SIZE, 5, - EGL10.EGL_GREEN_SIZE, 6, - EGL10.EGL_BLUE_SIZE, 5, - EGL10.EGL_ALPHA_SIZE, 4, - EGL10.EGL_DEPTH_SIZE, 16, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_SAMPLE_BUFFERS, 1, - EGL10.EGL_SAMPLES, 2, - EGL10.EGL_NONE }; - - protected int[] configAttribsGL_CovMSAA = { - EGL10.EGL_RED_SIZE, 5, - EGL10.EGL_GREEN_SIZE, 6, - EGL10.EGL_BLUE_SIZE, 5, - EGL10.EGL_ALPHA_SIZE, 4, - EGL10.EGL_DEPTH_SIZE, 16, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_COVERAGE_BUFFERS_NV, 1, - EGL_COVERAGE_SAMPLES_NV, 2, - EGL10.EGL_NONE }; - - protected int[] configAttribsGL_NoMSAA = { - EGL10.EGL_RED_SIZE, 5, - EGL10.EGL_GREEN_SIZE, 6, - EGL10.EGL_BLUE_SIZE, 5, - EGL10.EGL_ALPHA_SIZE, 4, - EGL10.EGL_DEPTH_SIZE, 16, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE }; - - protected int[] configAttribsGL_Good = { - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_DEPTH_SIZE, 16, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE }; - - protected int[] configAttribsGL_TestMSAA = { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_SAMPLE_BUFFERS, 1, - EGL10.EGL_SAMPLES, 2, - EGL10.EGL_NONE }; - */ - - protected int[] attribsNoMSAA = { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_SAMPLE_BUFFERS, 0, - EGL10.EGL_NONE }; - - public AndroidConfigChooser(int rbits, int gbits, int bbits, int abits, - int dbits, int sbits, int samples) { - redTarget = rbits; - greenTarget = gbits; - blueTarget = bbits; - alphaTarget = abits; - depthTarget = dbits; - stencilTarget = sbits; - numSamples = samples; - } - - public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - EGLConfig[] configs = null; - if (1 < numSamples) { - int[] attribs = new int[] { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_SAMPLE_BUFFERS, 1, - EGL10.EGL_SAMPLES, numSamples, - EGL10.EGL_NONE }; - configs = chooseConfigWithAttribs(egl, display, attribs); - if (configs == null) { - // No normal multisampling config was found. Try to create a - // coverage multisampling configuration, for the nVidia Tegra2. - // See the EGL_NV_coverage_sample documentation. - int[] attribsCov = { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_COVERAGE_BUFFERS_NV, 1, - EGL_COVERAGE_SAMPLES_NV, numSamples, - EGL10.EGL_NONE }; - configs = chooseConfigWithAttribs(egl, display, attribsCov); - if (configs == null) { - configs = chooseConfigWithAttribs(egl, display, attribsNoMSAA); - } else { - usingMultisampling = true; - usingCoverageMultisampling = true; - multisampleCount = numSamples; - } - } else { - usingMultisampling = true; - usingCoverageMultisampling = false; - multisampleCount = numSamples; - } - } else { - configs = chooseConfigWithAttribs(egl, display, attribsNoMSAA); - } - - if (configs == null) { - throw new IllegalArgumentException("No EGL configs match configSpec"); - } - - if (PApplet.DEBUG) { - for (EGLConfig config : configs) { - String configStr = "P3D - selected EGL config : " - + printConfig(egl, display, config); - System.out.println(configStr); - } - } - - // Now return the configuration that best matches the target one. - return chooseBestConfig(egl, display, configs); - } + @Override + protected void initFBOLayer() { + if (0 < sketch.frameCount) { + IntBuffer buf = allocateDirectIntBuffer(fboWidth * fboHeight); - public EGLConfig chooseBestConfig(EGL10 egl, EGLDisplay display, - EGLConfig[] configs) { - EGLConfig bestConfig = null; - float bestScore = Float.MAX_VALUE; - - for (EGLConfig config : configs) { - int gl = findConfigAttrib(egl, display, config, - EGL10.EGL_RENDERABLE_TYPE, 0); - boolean isGLES2 = (gl & EGL_OPENGL_ES2_BIT) != 0; - if (isGLES2) { - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); - - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); - - float score = 0.20f * PApplet.abs(r - redTarget) + - 0.20f * PApplet.abs(g - greenTarget) + - 0.20f * PApplet.abs(b - blueTarget) + - 0.15f * PApplet.abs(a - alphaTarget) + - 0.15f * PApplet.abs(d - depthTarget) + - 0.10f * PApplet.abs(s - stencilTarget); - - if (score < bestScore) { - // We look for the config closest to the target config. - // Closeness is measured by the score function defined above: - // we give more weight to the RGB components, followed by the - // alpha, depth and finally stencil bits. - bestConfig = config; - bestScore = score; - - redBits = r; - greenBits = g; - blueBits = b; - alphaBits = a; - depthBits = d; - stencilBits = s; - } - } - } + if (hasReadBuffer()) readBuffer(BACK); + readPixelsImpl(0, 0, fboWidth, fboHeight, RGBA, UNSIGNED_BYTE, buf); + bindTexture(TEXTURE_2D, glColorTex.get(frontTex)); + texSubImage2D(TEXTURE_2D, 0, 0, 0, fboWidth, fboHeight, RGBA, UNSIGNED_BYTE, buf); - if (PApplet.DEBUG) { - String configStr = "P3D - selected EGL config : " - + printConfig(egl, display, bestConfig); - System.out.println(configStr); - } - return bestConfig; - } + bindTexture(TEXTURE_2D, glColorTex.get(backTex)); + texSubImage2D(TEXTURE_2D, 0, 0, 0, fboWidth, fboHeight, RGBA, UNSIGNED_BYTE, buf); - protected String printConfig(EGL10 egl, EGLDisplay display, - EGLConfig config) { - int r = findConfigAttrib(egl, display, config, - EGL10.EGL_RED_SIZE, 0); - int g = findConfigAttrib(egl, display, config, - EGL10.EGL_GREEN_SIZE, 0); - int b = findConfigAttrib(egl, display, config, - EGL10.EGL_BLUE_SIZE, 0); - int a = findConfigAttrib(egl, display, config, - EGL10.EGL_ALPHA_SIZE, 0); - int d = findConfigAttrib(egl, display, config, - EGL10.EGL_DEPTH_SIZE, 0); - int s = findConfigAttrib(egl, display, config, - EGL10.EGL_STENCIL_SIZE, 0); - int type = findConfigAttrib(egl, display, config, - EGL10.EGL_RENDERABLE_TYPE, 0); - int nat = findConfigAttrib(egl, display, config, - EGL10.EGL_NATIVE_RENDERABLE, 0); - int bufSize = findConfigAttrib(egl, display, config, - EGL10.EGL_BUFFER_SIZE, 0); - int bufSurf = findConfigAttrib(egl, display, config, - EGL10.EGL_RENDER_BUFFER, 0); - - return String.format("EGLConfig rgba=%d%d%d%d depth=%d stencil=%d", - r,g,b,a,d,s) - + " type=" + type - + " native=" + nat - + " buffer size=" + bufSize - + " buffer surface=" + bufSurf + - String.format(" caveat=0x%04x", - findConfigAttrib(egl, display, config, - EGL10.EGL_CONFIG_CAVEAT, 0)); + bindTexture(TEXTURE_2D, 0); + bindFramebufferImpl(FRAMEBUFFER, 0); } + } - protected int findConfigAttrib(EGL10 egl, EGLDisplay display, - EGLConfig config, int attribute, int defaultValue) { - if (egl.eglGetConfigAttrib(display, config, attribute, tempValue)) { - return tempValue[0]; - } - return defaultValue; - } - - protected EGLConfig[] chooseConfigWithAttribs(EGL10 egl, - EGLDisplay display, - int[] configAttribs) { - // Get the number of minimally matching EGL configurations - int[] configCounts = new int[1]; - egl.eglChooseConfig(display, configAttribs, null, 0, configCounts); - - int count = configCounts[0]; - - if (count <= 0) { - //throw new IllegalArgumentException("No EGL configs match configSpec"); - return null; - } - - // Allocate then read the array of minimally matching EGL configs - EGLConfig[] configs = new EGLConfig[count]; - egl.eglChooseConfig(display, configAttribs, configs, count, configCounts); - return configs; - - // Get the number of minimally matching EGL configurations -// int[] num_config = new int[1]; -// egl.eglChooseConfig(display, configAttribsGL, null, 0, num_config); -// -// int numConfigs = num_config[0]; -// -// if (numConfigs <= 0) { -// throw new IllegalArgumentException("No EGL configs match configSpec"); -// } -// -// // Allocate then read the array of minimally matching EGL configs -// EGLConfig[] configs = new EGLConfig[numConfigs]; -// egl.eglChooseConfig(display, configAttribsGL, configs, numConfigs, -// num_config); - } + @Override + protected void clearFrontColorBuffer() { + // Need to front clear color buffer, otherwise one can lead to the screen not clearning + // properly in sketches that do not call background() continously in draw() but only at + // specific events. + framebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, + TEXTURE_2D, glColorTex.get(frontTex), 0); + clear(COLOR_BUFFER_BIT); + framebufferTexture2D(FRAMEBUFFER, COLOR_ATTACHMENT0, + TEXTURE_2D, glColorTex.get(backTex), 0); } @@ -564,28 +294,53 @@ public Tessellator(TessellatorCallback callback) { PGLU.gluTessCallback(tess, PGLU.GLU_TESS_ERROR, gluCallback); } + @Override + public void setCallback(int flag) { + PGLU.gluTessCallback(tess, flag, gluCallback); + } + + @Override + public void setWindingRule(int rule) { + setProperty(PGLU.GLU_TESS_WINDING_RULE, rule); + } + + public void setProperty(int property, int value) { + PGLU.gluTessProperty(tess, property, value); + } + + @Override public void beginPolygon() { - PGLU.gluTessBeginPolygon(tess, null); + beginPolygon(null); } - public void endPolygon() { - PGLU.gluTessEndPolygon(tess); + @Override + public void beginPolygon(Object data) { + PGLU.gluTessBeginPolygon(tess, data); } - public void setWindingRule(int rule) { - PGLU.gluTessProperty(tess, PGLU.GLU_TESS_WINDING_RULE, rule); + @Override + public void endPolygon() { + PGLU.gluTessEndPolygon(tess); } + @Override public void beginContour() { PGLU.gluTessBeginContour(tess); } + @Override public void endContour() { PGLU.gluTessEndContour(tess); } + @Override public void addVertex(double[] v) { - PGLU.gluTessVertex(tess, v, 0, v); + addVertex(v, 0, v); + } + + @Override + public void addVertex(double[] v, int n, Object data) { + PGLU.gluTessVertex(tess, v, n, data); } protected class GLUCallback extends PGLUtessellatorCallbackAdapter { @@ -685,6 +440,7 @@ protected FontOutline createFontOutline(char ch, Object font) { TESS_WINDING_NONZERO = PGLU.GLU_TESS_WINDING_NONZERO; TESS_WINDING_ODD = PGLU.GLU_TESS_WINDING_ODD; + TESS_EDGE_FLAG = PGLU.GLU_TESS_EDGE_FLAG; GENERATE_MIPMAP_HINT = GLES20.GL_GENERATE_MIPMAP_HINT; FASTEST = GLES20.GL_FASTEST; @@ -875,7 +631,6 @@ protected FontOutline createFontOutline(char ch, Object font) { STENCIL_TEST = GLES20.GL_STENCIL_TEST; DEPTH_TEST = GLES20.GL_DEPTH_TEST; DEPTH_WRITEMASK = GLES20.GL_DEPTH_WRITEMASK; - ALPHA_TEST = 0x0BC0; COLOR_BUFFER_BIT = GLES20.GL_COLOR_BUFFER_BIT; DEPTH_BUFFER_BIT = GLES20.GL_DEPTH_BUFFER_BIT; @@ -899,7 +654,7 @@ protected FontOutline createFontOutline(char ch, Object font) { DEPTH_COMPONENT24 = 0x81A6; DEPTH_COMPONENT32 = 0x81A7; - STENCIL_INDEX = GLES20.GL_STENCIL_INDEX; + STENCIL_INDEX = 6401; // GLES20.GL_STENCIL_INDEX is marked as deprecated STENCIL_INDEX1 = 0x8D46; STENCIL_INDEX4 = 0x8D47; STENCIL_INDEX8 = GLES20.GL_STENCIL_INDEX8; @@ -931,7 +686,6 @@ protected FontOutline createFontOutline(char ch, Object font) { RENDERBUFFER_INTERNAL_FORMAT = GLES20.GL_RENDERBUFFER_INTERNAL_FORMAT; MULTISAMPLE = -1; - POINT_SMOOTH = -1; LINE_SMOOTH = -1; POLYGON_SMOOTH = -1; } @@ -1089,9 +843,16 @@ public void depthRangef(float n, float f) { @Override public void viewport(int x, int y, int w, int h) { + float scale = getPixelScale(); + viewportImpl((int)scale * x, (int)(scale * y), (int)(scale * w), (int)(scale * h)); + } + + @Override + protected void viewportImpl(int x, int y, int w, int h) { GLES20.glViewport(x, y, w, h); } + ////////////////////////////////////////////////////////////////////////////// // Reading Pixels @@ -1101,6 +862,14 @@ public void readPixelsImpl(int x, int y, int width, int height, int format, int GLES20.glReadPixels(x, y, width, height, format, type, buffer); } + + @Override + protected void readPixelsImpl(int x, int y, int width, int height, int format, + int type, long offset) { + // TODO Auto-generated method stub + } + + ////////////////////////////////////////////////////////////////////////////// // Vertices @@ -1141,7 +910,7 @@ public void vertexAttrib3fv(int index, FloatBuffer values) { } @Override - public void vertexAttri4fv(int index, FloatBuffer values) { + public void vertexAttrib4fv(int index, FloatBuffer values) { GLES20.glVertexAttrib4fv(index, values); } @@ -1150,11 +919,6 @@ public void vertexAttribPointer(int index, int size, int type, boolean normalize GLES20.glVertexAttribPointer(index, size, type, normalized, stride, offset); } - @Override - public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, Buffer data) { - GLES20.glVertexAttribPointer(index, size, type, normalized, stride, data); - } - @Override public void enableVertexAttribArray(int index) { GLES20.glEnableVertexAttribArray(index); @@ -1166,20 +930,15 @@ public void disableVertexAttribArray(int index) { } @Override - public void drawArrays(int mode, int first, int count) { + public void drawArraysImpl(int mode, int first, int count) { GLES20.glDrawArrays(mode, first, count); } @Override - public void drawElements(int mode, int count, int type, int offset) { + public void drawElementsImpl(int mode, int count, int type, int offset) { GLES20.glDrawElements(mode, count, type, offset); } - @Override - public void drawElements(int mode, int count, int type, Buffer indices) { - GLES20.glDrawElements(mode, count, type, indices); - } - ////////////////////////////////////////////////////////////////////////////// // Rasterization @@ -1254,7 +1013,7 @@ public void texParameteri(int target, int pname, int param) { @Override public void texParameterf(int target, int pname, float param) { - gl.glTexParameterf(target, pname, param); + GLES20.glTexParameterf(target, pname, param); } @Override @@ -1645,11 +1404,6 @@ public void blendColor(float red, float green, float blue, float alpha) { GLES20.glBlendColor(red, green, blue, alpha); } - @Override - public void alphaFunc(int func, float ref) { - throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glAlphaFunc()")); - } - /////////////////////////////////////////////////////////// // Whole Framebuffer Operations @@ -1773,21 +1527,83 @@ public void getRenderbufferParameteriv(int target, int pname, IntBuffer params) @Override public void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { - throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glBlitFramebuffer()")); +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glBlitFramebuffer()")); } @Override public void renderbufferStorageMultisample(int target, int samples, int format, int width, int height) { - throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glRenderbufferStorageMultisample()")); +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glRenderbufferStorageMultisample()")); } @Override public void readBuffer(int buf) { - throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glReadBuffer()")); +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glReadBuffer()")); } @Override public void drawBuffer(int buf) { - throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glDrawBuffer()")); +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "glDrawBuffer()")); + } + + + @Override + protected int getFontAscent(Object font) { + // TODO Auto-generated method stub + return 0; } + + + @Override + protected int getFontDescent(Object font) { + // TODO Auto-generated method stub + return 0; + } + + + @Override + protected int getTextWidth(Object font, char[] buffer, int start, int stop) { + // TODO Auto-generated method stub + return 0; + } + + + @Override + protected Object getDerivedFont(Object font, float size) { + // TODO Auto-generated method stub + return null; + } + + ////////////////////////////////////////////////////////////////////////////// + + // Synchronization + + @Override + public long fenceSync(int condition, int flags) { + return 0; +// if (gl3es3 != null) { +// return gl3es3.glFenceSync(condition, flags); +// } else { +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "fenceSync()")); +// } + } + + @Override + public void deleteSync(long sync) { +// if (gl3es3 != null) { +// gl3es3.glDeleteSync(sync); +// } else { +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "deleteSync()")); +// } + } + + @Override + public int clientWaitSync(long sync, int flags, long timeout) { + return 0; +// if (gl3es3 != null) { +// return gl3es3.glClientWaitSync(sync, flags, timeout); +// } else { +// throw new RuntimeException(String.format(MISSING_GLFUNC_ERROR, "clientWaitSync()")); +// } + } + } diff --git a/core/src/processing/opengl/PGraphics2D.java b/libs/processing-core/src/main/java/processing/opengl/PGraphics2D.java similarity index 87% rename from core/src/processing/opengl/PGraphics2D.java rename to libs/processing-core/src/main/java/processing/opengl/PGraphics2D.java index b3fc4e890..2fdd47543 100644 --- a/core/src/processing/opengl/PGraphics2D.java +++ b/libs/processing-core/src/main/java/processing/opengl/PGraphics2D.java @@ -3,11 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -18,20 +20,16 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ package processing.opengl; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; - -import processing.core.PApplet; -import processing.core.PConstants; import processing.core.PGraphics; +import processing.core.PMatrix2D; import processing.core.PMatrix3D; import processing.core.PShape; import processing.core.PShapeSVG; -import processing.data.XML; + public class PGraphics2D extends PGraphicsOpenGL { @@ -156,7 +154,7 @@ public void camera(float eyeX, float eyeY, float eyeZ, @Override protected void defaultCamera() { - cameraEyeX = cameraEyeY = cameraEyeZ = 0; + eyeDist = 1; resetMatrix(); } @@ -233,6 +231,7 @@ public void shape(PShape shape, float x, float y, float z, } + ////////////////////////////////////////////////////////////// // SHAPE I/O @@ -243,30 +242,39 @@ static protected boolean isSupportedExtension(String extension) { } - static protected PShape loadShapeImpl(PGraphics pg, String filename, - String extension) { - PShapeSVG svg = null; + static protected PShape loadShapeImpl(PGraphics pg, + String filename, String extension) { + if (extension.equals("svg") || extension.equals("svgz")) { + PShapeSVG svg = new PShapeSVG(pg.parent.loadXML(filename)); + return PShapeOpenGL.createShape((PGraphicsOpenGL) pg, svg); + } + return null; + } - if (extension.equals("svg")) { - svg = new PShapeSVG(pg.parent.loadXML(filename)); - } else if (extension.equals("svgz")) { - try { - InputStream input = - new GZIPInputStream(pg.parent.createInput(filename)); - XML xml = new XML(PApplet.createReader(input)); - svg = new PShapeSVG(xml); - } catch (Exception e) { - e.printStackTrace(); - } - } + ////////////////////////////////////////////////////////////// - if (svg != null) { - PShapeOpenGL p2d = PShapeOpenGL.createShape2D((PGraphicsOpenGL)pg, svg); - return p2d; - } else { - return null; - } + // SCREEN TRANSFORMS + + + @Override + public float modelX(float x, float y, float z) { + showDepthWarning("modelX"); + return 0; + } + + + @Override + public float modelY(float x, float y, float z) { + showDepthWarning("modelY"); + return 0; + } + + + @Override + public float modelZ(float x, float y, float z) { + showDepthWarning("modelZ"); + return 0; } @@ -275,6 +283,19 @@ static protected PShape loadShapeImpl(PGraphics pg, String filename, // SHAPE CREATION +// @Override +// protected PShape createShapeFamily(int type) { +// return new PShapeOpenGL(this, type); +// } +// +// +// @Override +// protected PShape createShapePrimitive(int kind, float... p) { +// return new PShapeOpenGL(this, kind, p); +// } + + + /* @Override public PShape createShape(PShape source) { return PShapeOpenGL.createShape2D(this, source); @@ -308,7 +329,7 @@ static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, int type) { } else if (type == PShape.GEOMETRY) { shape = new PShapeOpenGL(pg, PShape.GEOMETRY); } - shape.is3D(false); + shape.set3D(false); return shape; } @@ -379,9 +400,10 @@ static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, shape.setParams(p); } - shape.is3D(false); + shape.set3D(false); return shape; } + */ ////////////////////////////////////////////////////////////// @@ -526,6 +548,19 @@ public float screenZ(float x, float y, float z) { return 0; } + @Override + public PMatrix2D getMatrix(PMatrix2D target) { + if (target == null) { + target = new PMatrix2D(); + } + // This set operation is well defined, since modelview is a 2D-only + // transformation matrix in the P2D renderer. + target.set(modelview.m00, modelview.m01, modelview.m03, + modelview.m10, modelview.m11, modelview.m13); + return target; + + } + @Override public PMatrix3D getMatrix(PMatrix3D target) { showVariationWarning("getMatrix"); diff --git a/libs/processing-core/src/main/java/processing/opengl/PGraphics2DX.java b/libs/processing-core/src/main/java/processing/opengl/PGraphics2DX.java new file mode 100755 index 000000000..60fee8e16 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/opengl/PGraphics2DX.java @@ -0,0 +1,2055 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.opengl; + +import static processing.core.PApplet.println; + +import java.net.URL; +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import processing.core.PApplet; +import processing.core.PGraphics; +import processing.core.PImage; +import processing.core.PMatrix3D; +import processing.core.PShape; +import processing.core.PShapeSVG; + +/** + * Super fast OpenGL 2D renderer originally contributed by Miles Fogle: + * https://github.com/hazmatsuitor + * + * It speeds-up rendering of 2D geometry by essentially two key optimizations: packing all the + * vertex data in a single VBO, and using a custom stroke tessellator (see StrokeRenderer class + * at the end). There are a number of other, less critical optimizations, for example using a single + * shader for textured and non-textured geometry and a depth algorithm that allows stacking a large + * number of 2D shapes without z-fighting (so occlusion is based on drawing order). + * + * Some notes from Miles: + * + * for testing purposes, I found it easier to create a separate class and avoid + * touching existing code for now, rather than directly editing PGraphics2D/PGraphicsOpenGL + * if this code becomes the new P2D implementation, then it will be properly migrated/integrated + + * NOTE: this implementation doesn't use some of Processing's OpenGL wrappers + * (e.g. PShader, Texture) because I found it more convenient to handle them manually + * it could probably be made to use those classes with a bit of elbow grease and a spot of guidance + * but it may not be worth it - I doubt it would reduce complexity much, if at all + * (if there are reasons we need to use those classes, let me know) + * + */ + +//TODO: track debug performance stats +public final class PGraphics2DX extends PGraphicsOpenGL { + static final String NON_2D_SHAPE_ERROR = "The shape object is not 2D, cannot be displayed with this renderer"; + static final String STROKE_PERSPECTIVE_ERROR = "Strokes cannot be perspective-corrected in 2D"; + + static final String NON_2D_SHADER_ERROR = "This shader cannot be used for 2D rendering"; + static final String WRONG_SHADER_PARAMS = "The P2D renderer does not accept shaders of different tyes"; + + static protected final int SHADER2D = 7; + + // Uses the implementations in the parent PGraphicsOpenGL class, which is needed to to draw obj files + // and apply shader filters. + protected boolean useParentImpl = false; + + protected boolean initialized; + + protected PGL.Tessellator tess; + + protected PShader twoShader; + protected PShader defTwoShader; + + protected int positionLoc; + protected int colorLoc; + protected int texCoordLoc; + protected int texFactorLoc; + + protected int transformLoc; + protected int texScaleLoc; + + static protected URL defP2DShaderVertURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/P2DVert.glsl"); + static protected URL defP2DShaderFragURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/P2DFrag.glsl"); + + + public PGraphics2DX() { + super(); + initTess(); + initVerts(); + } + + + ////////////////////////////////////////////////////////////// + + // RENDERER SUPPORT QUERIES + + + @Override + public boolean is2D() { + return true; + } + + + @Override + public boolean is3D() { + return false; + } + + + ////////////////////////////////////////////////////////////// + + // RENDERING + + + @Override + public void beginDraw() { + super.beginDraw(); + if (!useParentImpl) { + pgl.depthFunc(PGL.LESS); + depth = 1.0f; + } + } + + + @Override + public void flush() { + // If no vertices where created with the base implementation, then flush() will do nothing. + super.flush(); + flushBuffer(); + } + + + // These two methods are meant for debugging (comparing the new and old P2D renderers) + // and will go away. + + + public void useOldP2D() { + useParentImpl = true; + pgl.depthFunc(PGL.LEQUAL); + } + + + public void useNewP2D() { + useParentImpl = false; + pgl.depthFunc(PGL.LESS); + } + + + ////////////////////////////////////////////////////////////// + + // HINTS + + + @Override + public void hint(int which) { + if (which == ENABLE_STROKE_PERSPECTIVE) { + showWarning(STROKE_PERSPECTIVE_ERROR); + return; + } + super.hint(which); + } + + + ////////////////////////////////////////////////////////////// + + // PROJECTION + + + @Override + public void ortho() { + showMethodWarning("ortho"); + } + + + @Override + public void ortho(float left, float right, + float bottom, float top) { + showMethodWarning("ortho"); + } + + + @Override + public void ortho(float left, float right, + float bottom, float top, + float near, float far) { + showMethodWarning("ortho"); + } + + + @Override + public void perspective() { + showMethodWarning("perspective"); + } + + + @Override + public void perspective(float fov, float aspect, float zNear, float zFar) { + showMethodWarning("perspective"); + } + + + @Override + public void frustum(float left, float right, float bottom, float top, + float znear, float zfar) { + showMethodWarning("frustum"); + } + + + @Override + protected void defaultPerspective() { + super.ortho(0, width, -height, 0, -1, +1); + } + + + ////////////////////////////////////////////////////////////// + + // CAMERA + + + @Override + public void beginCamera() { + showMethodWarning("beginCamera"); + } + + + @Override + public void endCamera() { + showMethodWarning("endCamera"); + } + + + @Override + public void camera() { + showMethodWarning("camera"); + } + + + @Override + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + showMethodWarning("camera"); + } + + + @Override + protected void defaultCamera() { + eyeDist = 1; + resetMatrix(); + } + + + ////////////////////////////////////////////////////////////// + + // SHAPE + + + @Override + public void shape(PShape shape) { + if (shape.is2D()) { + if (!useParentImpl) { + useOldP2D(); + super.shape(shape); + useNewP2D(); + } else { + super.shape(shape); + } + } else { + showWarning(NON_2D_SHAPE_ERROR); + } + } + + + @Override + public void shape(PShape shape, float x, float y) { + if (shape.is2D()) { + if (!useParentImpl) { + useOldP2D(); + super.shape(shape, x, y); + useNewP2D(); + } else { + super.shape(shape, x, y); + } + } else { + showWarning(NON_2D_SHAPE_ERROR); + } + } + + + @Override + public void shape(PShape shape, float a, float b, float c, float d) { + if (shape.is2D()) { + if (!useParentImpl) { + useOldP2D(); + super.shape(shape, a, b, c, d); + useNewP2D(); + } else { + super.shape(shape, a, b, c, d); + } + } else { + showWarning(NON_2D_SHAPE_ERROR); + } + } + + + @Override + public void shape(PShape shape, float x, float y, float z) { + showDepthWarningXYZ("shape"); + } + + + @Override + public void shape(PShape shape, float x, float y, float z, + float c, float d, float e) { + showDepthWarningXYZ("shape"); + } + + + + ////////////////////////////////////////////////////////////// + + // SHAPE I/O + + + static protected boolean isSupportedExtension(String extension) { + return extension.equals("svg") || extension.equals("svgz"); + } + + + static protected PShape loadShapeImpl(PGraphics pg, + String filename, String extension) { + if (extension.equals("svg") || extension.equals("svgz")) { + PShapeSVG svg = new PShapeSVG(pg.parent.loadXML(filename)); + return PShapeOpenGL.createShape((PGraphicsOpenGL) pg, svg); + } + return null; + } + + + ////////////////////////////////////////////////////////////// + + // SCREEN TRANSFORMS + + + @Override + public float modelX(float x, float y, float z) { + showDepthWarning("modelX"); + return 0; + } + + + @Override + public float modelY(float x, float y, float z) { + showDepthWarning("modelY"); + return 0; + } + + + @Override + public float modelZ(float x, float y, float z) { + showDepthWarning("modelZ"); + return 0; + } + + + ////////////////////////////////////////////////////////////// + + // VERTEX SHAPES + + + @Override + public void texture(PImage image) { + super.texture(image); + + if (image == null) { + return; + } + + Texture t = currentPG.getTexture(image); + texWidth = t.width; + texHeight = t.height; + imageTex = t.glName; + textureImpl(imageTex); + } + + + @Override + public void beginShape(int kind) { + if (useParentImpl) { + super.beginShape(kind); + return; + } + + shapeType = kind; + vertCount = 0; + contourCount = 0; + } + + + @Override + public void endShape(int mode) { + if (useParentImpl) { + super.endShape(mode); + return; + } + + //end the current contour + appendContour(vertCount); + + if (fill) { + incrementDepth(); + + if (shapeType == POLYGON) { + if (knownConvexPolygon) { + for (int i = 2; i < vertCount; ++i) { + check(3); + vertexImpl(shapeVerts[0]); + vertexImpl(shapeVerts[i - 1]); + vertexImpl(shapeVerts[i]); + } + + knownConvexPolygon = false; + } else { + tess.beginPolygon(this); + tess.beginContour(); + + int c = 0; + for (int i = 0; i < vertCount; ++i) { + if (contours[c] == i) { + tess.endContour(); + tess.beginContour(); + c++; //lol no, this is java + } + + tempDoubles[0] = shapeVerts[i].x; + tempDoubles[1] = shapeVerts[i].y; + tess.addVertex(tempDoubles, 0, shapeVerts[i]); + } + tess.endContour(); + tess.endPolygon(); + } + } else if (shapeType == QUAD_STRIP) { + for (int i = 0; i <= vertCount - 4; i += 2) { + check(6); + vertexImpl(shapeVerts[i + 0]); + vertexImpl(shapeVerts[i + 1]); + vertexImpl(shapeVerts[i + 2]); + vertexImpl(shapeVerts[i + 1]); + vertexImpl(shapeVerts[i + 2]); + vertexImpl(shapeVerts[i + 3]); + } + } else if (shapeType == QUADS) { + for (int i = 0; i <= vertCount - 4; i += 4) { + check(6); + vertexImpl(shapeVerts[i + 0]); + vertexImpl(shapeVerts[i + 1]); + vertexImpl(shapeVerts[i + 2]); + vertexImpl(shapeVerts[i + 0]); + vertexImpl(shapeVerts[i + 2]); + vertexImpl(shapeVerts[i + 3]); + } + } else if (shapeType == TRIANGLE_STRIP) { + for (int i = 0; i <= vertCount - 3; i += 1) { + check(3); + vertexImpl(shapeVerts[i + 0]); + vertexImpl(shapeVerts[i + 1]); + vertexImpl(shapeVerts[i + 2]); + } + } else if (shapeType == TRIANGLE_FAN) { + for (int i = 0; i <= vertCount - 3; i += 1) { + check(3); + vertexImpl(shapeVerts[0 + 0]); + vertexImpl(shapeVerts[i + 1]); + vertexImpl(shapeVerts[i + 2]); + } + + //close the fan + if (vertCount >= 3) { + check(3); + vertexImpl(shapeVerts[0]); + vertexImpl(shapeVerts[vertCount - 1]); + vertexImpl(shapeVerts[1]); + } + } else if (shapeType == TRIANGLES) { + for (int i = 0; i <= vertCount - 3; i += 3) { + check(3); + vertexImpl(shapeVerts[i + 0]); + vertexImpl(shapeVerts[i + 1]); + vertexImpl(shapeVerts[i + 2]); + } + } + } + + if (stroke) { + incrementDepth(); + + if (shapeType == POLYGON) { + if (vertCount < 3) { + return; + } + + int c = 0; + sr.beginLine(); + for (int i = 0; i < vertCount; ++i) { + if (contours[c] == i) { + sr.endLine(mode == CLOSE); + sr.beginLine(); + c++; + } + + sr.lineVertex(shapeVerts[i].x, shapeVerts[i].y); + } + sr.endLine(mode == CLOSE); + } else if (shapeType == QUAD_STRIP) { + for (int i = 0; i <= vertCount - 4; i += 2) { + sr.beginLine(); + sr.lineVertex(shapeVerts[i + 0].x, shapeVerts[i + 0].y); + sr.lineVertex(shapeVerts[i + 1].x, shapeVerts[i + 1].y); + sr.lineVertex(shapeVerts[i + 3].x, shapeVerts[i + 3].y); + sr.lineVertex(shapeVerts[i + 2].x, shapeVerts[i + 2].y); + sr.endLine(true); + } + } else if (shapeType == QUADS) { + for (int i = 0; i <= vertCount - 4; i += 4) { + sr.beginLine(); + sr.lineVertex(shapeVerts[i + 0].x, shapeVerts[i + 0].y); + sr.lineVertex(shapeVerts[i + 1].x, shapeVerts[i + 1].y); + sr.lineVertex(shapeVerts[i + 2].x, shapeVerts[i + 2].y); + sr.lineVertex(shapeVerts[i + 3].x, shapeVerts[i + 3].y); + sr.endLine(true); + } + } else if (shapeType == TRIANGLE_STRIP) { + for (int i = 0; i <= vertCount - 3; i += 1) { + sr.beginLine(); + sr.lineVertex(shapeVerts[i + 0].x, shapeVerts[i + 0].y); + sr.lineVertex(shapeVerts[i + 1].x, shapeVerts[i + 1].y); + sr.lineVertex(shapeVerts[i + 2].x, shapeVerts[i + 2].y); + sr.endLine(true); + } + } else if (shapeType == TRIANGLE_FAN) { + for (int i = 0; i <= vertCount - 3; i += 1) { + sr.beginLine(); + sr.lineVertex(shapeVerts[0 + 0].x, shapeVerts[0 + 0].y); + sr.lineVertex(shapeVerts[i + 1].x, shapeVerts[i + 1].y); + sr.lineVertex(shapeVerts[i + 2].x, shapeVerts[i + 2].y); + sr.endLine(true); + } + + //close the fan + if (vertCount >= 3) { + sr.beginLine(); + sr.lineVertex(shapeVerts[0].x, shapeVerts[0].y); + sr.lineVertex(shapeVerts[vertCount - 1].x, shapeVerts[vertCount - 1].y); + sr.lineVertex(shapeVerts[1].x, shapeVerts[1].y); + sr.endLine(true); + } + } else if (shapeType == TRIANGLES) { + for (int i = 0; i <= vertCount - 3; i += 3) { + sr.beginLine(); + sr.lineVertex(shapeVerts[i + 0].x, shapeVerts[i + 0].y); + sr.lineVertex(shapeVerts[i + 1].x, shapeVerts[i + 1].y); + sr.lineVertex(shapeVerts[i + 2].x, shapeVerts[i + 2].y); + sr.endLine(true); + } + } else if (shapeType == LINES) { + for (int i = 0; i <= vertCount - 2; i += 2) { + TessVertex s1 = shapeVerts[i + 0]; + TessVertex s2 = shapeVerts[i + 1]; + singleLine(s1.x, s1.y, s2.x, s2.y, strokeColor); + } + } else if (shapeType == POINTS) { + for (int i = 0; i <= vertCount - 1; i += 1) { + singlePoint(shapeVerts[i].x, shapeVerts[i].y, strokeColor); + } + } + } + } + + + @Override + public void beginContour() { + super.beginContour(); + if (useParentImpl) { + return; + } + + //XXX: not sure what the exact behavior should be for invalid calls to begin/endContour() + //but this should work for valid cases for now + appendContour(vertCount); + } + + + @Override + public void vertex(float x, float y) { + if (useParentImpl) { + super.vertex(x, y); + return; + } + + curveVerts = 0; + shapeVertex(x, y, 0, 0, fillColor, 0); + } + + + @Override + public void vertex(float x, float y, float u, float v) { + if (useParentImpl) { + super.vertex(x, y, u, v); + return; + } + + curveVerts = 0; + textureImpl(imageTex); + shapeVertex(x, y, u, v, tint? tintColor : 0xFFFFFFFF, 1); + } + + + @Override + public void vertex(float x, float y, float z) { + showDepthWarningXYZ("vertex"); + } + + + @Override + public void vertex(float x, float y, float z, float u, float v) { + showDepthWarningXYZ("vertex"); + } + + + ////////////////////////////////////////////////////////////// + + // BEZIER VERTICES + + + //this method is almost wholesale copied from PGraphics.bezierVertex() + //TODO: de-duplicate this code if there is a convenient way to do so + @Override + public void bezierVertex(float x2, float y2, float x3, float y3, float x4, float y4) { + if (useParentImpl) { + super.bezierVertex(x2, y2, x3, y3, x4, y4); + return; + } + + bezierInitCheck(); +// bezierVertexCheck(); //TODO: re-implement this (and other run-time sanity checks) + PMatrix3D draw = bezierDrawMatrix; + + //(these are the only lines that are different) + float x1 = shapeVerts[vertCount - 1].x; + float y1 = shapeVerts[vertCount - 1].y; + + float xplot1 = draw.m10*x1 + draw.m11*x2 + draw.m12*x3 + draw.m13*x4; + float xplot2 = draw.m20*x1 + draw.m21*x2 + draw.m22*x3 + draw.m23*x4; + float xplot3 = draw.m30*x1 + draw.m31*x2 + draw.m32*x3 + draw.m33*x4; + + float yplot1 = draw.m10*y1 + draw.m11*y2 + draw.m12*y3 + draw.m13*y4; + float yplot2 = draw.m20*y1 + draw.m21*y2 + draw.m22*y3 + draw.m23*y4; + float yplot3 = draw.m30*y1 + draw.m31*y2 + draw.m32*y3 + draw.m33*y4; + + for (int j = 0; j < bezierDetail; j++) { + x1 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + shapeVertex(x1, y1, 0, 0, fillColor, 0); + } + } + + + @Override + public void bezierVertex(float x2, float y2, float z2, + float x3, float y3, float z3, + float x4, float y4, float z4) { + showDepthWarningXYZ("bezierVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // QUADRATIC BEZIER VERTICES + + + //this method is almost wholesale copied from PGraphics.quadraticVertex() + //TODO: de-duplicate this code if there is a convenient way to do so + @Override + public void quadraticVertex(float cx, float cy, + float x3, float y3) { + if (useParentImpl) { + super.quadraticVertex(cx, cy, x3, y3); + return; + } + + //(these are the only lines that are different) + float x1 = shapeVerts[vertCount - 1].x; + float y1 = shapeVerts[vertCount - 1].y; + + //TODO: optimize this? + bezierVertex(x1 + ((cx-x1)*2/3.0f), y1 + ((cy-y1)*2/3.0f), + x3 + ((cx-x3)*2/3.0f), y3 + ((cy-y3)*2/3.0f), + x3, y3); + } + + + @Override + public void quadraticVertex(float x2, float y2, float z2, + float x4, float y4, float z4) { + showDepthWarningXYZ("quadVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // CURVE VERTICES + + + //curve vertices + private float cx1, cy1, cx2, cy2, cx3, cy3, cx4, cy4; + private int curveVerts; + + + @Override + public void curveVertex(float x, float y) { + if (useParentImpl) { + super.curveVertex(x, y); + return; + } + +// curveVertexCheck(); //TODO: re-implement this (and other runtime checks) + + curveInitCheck(); + + cx1 = cx2; + cx2 = cx3; + cx3 = cx4; + + cy1 = cy2; + cy2 = cy3; + cy3 = cy4; + + cx4 = x; + cy4 = y; + + curveVerts += 1; + + if (curveVerts > 3) { + println("drawing curve..."); + + PMatrix3D draw = curveDrawMatrix; + + float xplot1 = draw.m10*cx1 + draw.m11*cx2 + draw.m12*cx3 + draw.m13*cx4; + float xplot2 = draw.m20*cx1 + draw.m21*cx2 + draw.m22*cx3 + draw.m23*cx4; + float xplot3 = draw.m30*cx1 + draw.m31*cx2 + draw.m32*cx3 + draw.m33*cx4; + + float yplot1 = draw.m10*cy1 + draw.m11*cy2 + draw.m12*cy3 + draw.m13*cy4; + float yplot2 = draw.m20*cy1 + draw.m21*cy2 + draw.m22*cy3 + draw.m23*cy4; + float yplot3 = draw.m30*cy1 + draw.m31*cy2 + draw.m32*cy3 + draw.m33*cy4; + + float x0 = cx2; + float y0 = cy2; + + if (curveVerts == 4) { + shapeVertex(x0, y0, 0, 0, fillColor, 0); + } + + for (int j = 0; j < curveDetail; j++) { + x0 += xplot1; xplot1 += xplot2; xplot2 += xplot3; + y0 += yplot1; yplot1 += yplot2; yplot2 += yplot3; + shapeVertex(x0, y0, 0, 0, fillColor, 0); + } + } + } + + + @Override + public void curveVertex(float x, float y, float z) { + showDepthWarningXYZ("curveVertex"); + } + + + ////////////////////////////////////////////////////////////// + + // PRIMITIVES + + + /* + * Re-implementations of the various shape drawing methods. + * + * Ideally we could just call the versions in PGraphics, + * since most of those will work correctly without modification, + * but there's no good way to do that in Java, + * so as long as we're inheriting from PGraphicsOpenGL, + * we need to re-implement them. + */ + + + @Override + public void quad(float x1, float y1, float x2, float y2, + float x3, float y3, float x4, float y4) { + if (useParentImpl) { + super.quad(x1, y1, x2, y2, x3, y3, x4, y4); + return; + } + + beginShape(QUADS); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + vertex(x4, y4); + endShape(); + } + + + @Override + public void triangle(float x1, float y1, float x2, float y2, + float x3, float y3) { + if (useParentImpl) { + super.triangle(x1, y1, x2, y2, x3, y3); + return; + } + + beginShape(TRIANGLES); + vertex(x1, y1); + vertex(x2, y2); + vertex(x3, y3); + endShape(); + } + + + @Override + public void ellipseImpl(float a, float b, float c, float d) { + if (useParentImpl) { + super.ellipseImpl(a, b, c, d); + return; + } + + beginShape(POLYGON); + + //convert corner/diameter to center/radius + float rx = c * 0.5f; + float ry = d * 0.5f; + float x = a + rx; + float y = b + ry; + + //since very wide stroke and/or very small radius might cause the + //stroke to account for a significant portion of the overall radius, + //we take it into account when calculating detail, just to be safe + int segments = circleDetail(PApplet.max(rx, ry) + (stroke? strokeWeight : 0), TWO_PI); + float step = TWO_PI / segments; + + float cos = PApplet.cos(step); + float sin = PApplet.sin(step); + float dx = 0, dy = 1; + for (int i = 0; i < segments; ++i) { + shapeVertex(x + dx * rx, y + dy * ry, 0, 0, fillColor, 0); + //this is the equivalent of multiplying the vector by the 2x2 rotation matrix [[cos -sin] [sin cos]] + float tempx = dx * cos - dy * sin; + dy = dx * sin + dy * cos; + dx = tempx; + } + + knownConvexPolygon = true; + endShape(CLOSE); + } + + + @Override + public void line(float x1, float y1, float x2, float y2) { + if (useParentImpl) { + super.line(x1, y1, x2, y2); + return; + } + + incrementDepth(); + singleLine(x1, y1, x2, y2, strokeColor); + } + + + @Override + public void point(float x, float y) { + if (useParentImpl) { + super.point(x, y); + return; + } + + incrementDepth(); + singlePoint(x, y, strokeColor); + } + + + @Override + protected void arcImpl(float x, float y, float w, float h, float start, float stop, int mode) { + if (useParentImpl) { + super.arcImpl(x, y, w, h, start, stop, mode); + return; + } + + //INVARIANT: stop > start + //INVARIANT: stop - start <= TWO_PI + + //convert corner/diameter to center/radius + w *= 0.5f; + h *= 0.5f; + x += w; + y += h; + + float diff = stop - start; + int segments = circleDetail(PApplet.max(w, h), diff); + float step = diff / segments; + + beginShape(POLYGON); + + //no constant is defined for the default arc mode, so we just use a literal 0 + //(this is consistent with code elsewhere) + if (mode == 0 || mode == PIE) { + vertex(x, y); + } + + if (mode == 0) { + //kinda hacky way to disable drawing a stroke along the first edge + appendContour(vertCount); + } + + float dx = PApplet.cos(start); + float dy = PApplet.sin(start); + float c = PApplet.cos(step); + float s = PApplet.sin(step); + for (int i = 0; i <= segments; ++i) { + shapeVertex(x + dx * w, y + dy * h, 0, 0, fillColor, 0); + //this is the equivalent of multiplying the vector by the 2x2 rotation matrix [[c -s] [s c]] + float tempx = dx * c - dy * s; + dy = dx * s + dy * c; + dx = tempx; + } + + //for the case `(mode == PIE || mode == 0) && diff > HALF_PI`, the polygon + //will not actually be convex, but due to known vertex order, we can still safely tessellate as if it is + knownConvexPolygon = true; + if (mode == CHORD || mode == PIE) { + endShape(CLOSE); + } else { + endShape(); + } + } + + + @Override + protected void rectImpl(float x1, float y1, float x2, float y2, + float tl, float tr, float br, float bl) { + if (useParentImpl) { + super.rectImpl(x1, y1, x2, y2, tl, tr, br, bl); + return; + } + + beginShape(); + if (tr != 0) { + vertex(x2-tr, y1); + quadraticVertex(x2, y1, x2, y1+tr); + } else { + vertex(x2, y1); + } + if (br != 0) { + vertex(x2, y2-br); + quadraticVertex(x2, y2, x2-br, y2); + } else { + vertex(x2, y2); + } + if (bl != 0) { + vertex(x1+bl, y2); + quadraticVertex(x1, y2, x1, y2-bl); + } else { + vertex(x1, y2); + } + if (tl != 0) { + vertex(x1, y1+tl); + quadraticVertex(x1, y1, x1+tl, y1); + } else { + vertex(x1, y1); + } + knownConvexPolygon = true; + endShape(CLOSE); + } + + + ////////////////////////////////////////////////////////////// + + // BOX + + + @Override + public void box(float w, float h, float d) { + showMethodWarning("box"); + } + + + ////////////////////////////////////////////////////////////// + + // SPHERE + + + @Override + public void sphere(float r) { + showMethodWarning("sphere"); + } + + + ////////////////////////////////////////////////////////////// + + // PIXELS + + + @Override + public void loadPixels() { + super.loadPixels(); + + allocatePixels(); + readPixels(); + } + + + @Override + public void updatePixels() { + super.updatePixels(); + image(this, 0, 0, width * 2, height * 2, 0, 0, pixelWidth, pixelHeight); + flushBuffer(); + } + + + ////////////////////////////////////////////////////////////// + + // CLIPPING + + /* + @Override + public void clipImpl(float x1, float y1, float x2, float y2) { + //XXX: exactly the same as the implementation in PGraphicsOpenGL, + //but calls flushBuffer() instead of flush() + flushBuffer(); + pgl.enable(PGL.SCISSOR_TEST); + + float h = y2 - y1; + clipRect[0] = (int)x1; + clipRect[1] = (int)(height - y1 - h); + clipRect[2] = (int)(x2 - x1); + clipRect[3] = (int)h; + pgl.scissor(clipRect[0], clipRect[1], clipRect[2], clipRect[3]); + + clip = true; + } + + @Override + public void noClip() { + //XXX: exactly the same as the implementation in PGraphicsOpenGL, + //but calls flushBuffer() instead of flush() + if (clip) { + flushBuffer(); + pgl.disable(PGL.SCISSOR_TEST); + clip = false; + } + } +*/ + + + ////////////////////////////////////////////////////////////// + + // TEXT + + + //NOTE: a possible improvement to text rendering performance is to batch all glyphs + //from the same texture page together instead of rendering each char strictly in sequence. + //it remains to be seen whether this would improve performance in practice + //(I don't know how common it is for a font to occupy multiple texture pages) + + @Override + protected void textCharModelImpl(FontTexture.TextureInfo info, + float x0, float y0, float x1, float y1) { + incrementDepth(); + check(6); + textureImpl(textTex.textures[info.texIndex].glName); + vertexImpl(x0, y0, info.u0, info.v0, fillColor, 1); + vertexImpl(x1, y0, info.u1, info.v0, fillColor, 1); + vertexImpl(x0, y1, info.u0, info.v1, fillColor, 1); + vertexImpl(x1, y0, info.u1, info.v0, fillColor, 1); + vertexImpl(x0, y1, info.u0, info.v1, fillColor, 1); + vertexImpl(x1, y1, info.u1, info.v1, fillColor, 1); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX MORE! + + + @Override + protected void begin2D() { + pushProjection(); + defaultPerspective(); + pushMatrix(); + defaultCamera(); + } + + + @Override + protected void end2D() { + popMatrix(); + popProjection(); + } + + + ////////////////////////////////////////////////////////////// + + // SHADER FILTER + + + + @Override + public void filter(PShader shader) { + // TODO: not working... the loadShader() method uses the P2 vertex stage + // The filter method needs to use the geometry-generation in the base class. + // We could re-implement it here, but this is easier. + if (!useParentImpl) { + useOldP2D(); + super.filter(shader); + useNewP2D(); + } else { + super.filter(shader); + } + } + + + + ////////////////////////////////////////////////////////////// + + // SHADER API + + + @Override + public PShader loadShader(String fragFilename) { + if (fragFilename == null || fragFilename.equals("")) { + PGraphics.showWarning(MISSING_FRAGMENT_SHADER); + return null; + } + + PShader shader = new PShader(parent); + + shader.setFragmentShader(fragFilename); + String[] vertSource = pgl.loadVertexShader(defP2DShaderVertURL); + shader.setVertexShader(vertSource); + + return shader; + } + + + @Override + public void shader(PShader shader) { + if (useParentImpl) { + super.shader(shader); + return; + } + flushBuffer(); // Flushing geometry drawn with a different shader. + + if (shader != null) shader.init(); + boolean res = checkShaderLocs(shader); + if (res) { + twoShader = shader; + shader.type = SHADER2D; + } else { + PGraphics.showWarning(NON_2D_SHADER_ERROR); + } + } + + + @Override + public void shader(PShader shader, int kind) { + if (useParentImpl) { + super.shader(shader, kind); + return; + } + PGraphics.showWarning(WRONG_SHADER_PARAMS); + } + + + @Override + public void resetShader() { + if (useParentImpl) { + super.resetShader(); + return; + } + flushBuffer(); + twoShader = null; + } + + + @Override + public void resetShader(int kind) { + if (useParentImpl) { + super.resetShader(kind); + return; + } + PGraphics.showWarning(WRONG_SHADER_PARAMS); + } + + + ////////////////////////////////////////////////////////////// + + // MATRIX TRANSFORMATIONS + + + @Override + public void translate(float tx, float ty, float tz) { + showDepthWarningXYZ("translate"); + } + + @Override + public void rotateX(float angle) { + showDepthWarning("rotateX"); + } + + @Override + public void rotateY(float angle) { + showDepthWarning("rotateY"); + } + + @Override + public void rotateZ(float angle) { + showDepthWarning("rotateZ"); + } + + @Override + public void rotate(float angle, float vx, float vy, float vz) { + showVariationWarning("rotate"); + } + + @Override + public void applyMatrix(PMatrix3D source) { + showVariationWarning("applyMatrix"); + } + + @Override + public void applyMatrix(float n00, float n01, float n02, float n03, + float n10, float n11, float n12, float n13, + float n20, float n21, float n22, float n23, + float n30, float n31, float n32, float n33) { + showVariationWarning("applyMatrix"); + } + + @Override + public void scale(float sx, float sy, float sz) { + showDepthWarningXYZ("scale"); + } + + ////////////////////////////////////////////////////////////// + + // SCREEN AND MODEL COORDS + + + @Override + public float screenX(float x, float y, float z) { + showDepthWarningXYZ("screenX"); + return 0; + } + + @Override + public float screenY(float x, float y, float z) { + showDepthWarningXYZ("screenY"); + return 0; + } + + @Override + public float screenZ(float x, float y, float z) { + showDepthWarningXYZ("screenZ"); + return 0; + } + + @Override + public PMatrix3D getMatrix(PMatrix3D target) { + showVariationWarning("getMatrix"); + return target; + } + + @Override + public void setMatrix(PMatrix3D source) { + showVariationWarning("setMatrix"); + } + + ////////////////////////////////////////////////////////////// + + // LIGHTS + + + @Override + public void lights() { + showMethodWarning("lights"); + } + + @Override + public void noLights() { + showMethodWarning("noLights"); + } + + @Override + public void ambientLight(float red, float green, float blue) { + showMethodWarning("ambientLight"); + } + + @Override + public void ambientLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("ambientLight"); + } + + @Override + public void directionalLight(float red, float green, float blue, + float nx, float ny, float nz) { + showMethodWarning("directionalLight"); + } + + @Override + public void pointLight(float red, float green, float blue, + float x, float y, float z) { + showMethodWarning("pointLight"); + } + + @Override + public void spotLight(float red, float green, float blue, + float x, float y, float z, + float nx, float ny, float nz, + float angle, float concentration) { + showMethodWarning("spotLight"); + } + + @Override + public void lightFalloff(float constant, float linear, float quadratic) { + showMethodWarning("lightFalloff"); + } + + @Override + public void lightSpecular(float v1, float v2, float v3) { + showMethodWarning("lightSpecular"); + } + + + ////////////////////////////////////////////////////////////// + + // PRIVATE IMPLEMENTATION + + + //maxVerts can be tweaked for memory/performance trade-off + //in my testing, performance seems to plateau after around 6000 (= 2000*3) + //memory usage should be around ~165kb for 6000 verts + final private int maxVerts = 2000*3; + final private int vertSize = 7*Float.BYTES; //xyzuvcf + private float[] vertexData = new float[maxVerts*7]; + private int usedVerts = 0; + + private float depth = 1.0f; + + private int imageTex; + private int tex; + private int vbo; + private int texWidth, texHeight; + + // Determination of the smallest increments and largest-greater-than-minus-one + // https://en.wikipedia.org/wiki/Half-precision_floating-point_format + + // Using the smallest positive normal number in half (16-bit) precision, which is how the depth + // buffer is initialized in mobile + private float smallestDepthIncrement = (float)Math.pow(2, -14); + + // As the limit for the depth increase, we take the minus the largest number less than one in + // half (16-bit) precision + private float largestNumberLessThanOne = 1 - (float)Math.pow(2, -11); + + private void incrementDepth() { + // By resetting the depth buffer when needed, we are able to have arbitrarily many + // layers, unlimited by depth buffer precision. In practice, the precision of this + // algorithm seems to be acceptable (exactly (1 + 1 - pow(2, -11))/pow(2, -14) = 32,760 layers) + // for mobile. + if (depth < -largestNumberLessThanOne) { + flushBuffer(); + pgl.clear(PGL.DEPTH_BUFFER_BIT); + // Depth test will fail at depth = 1.0 after clearing the depth buffer, + // But since we always increment before drawing anything, this should be okay + depth = 1.0f; + } + + depth -= smallestDepthIncrement; + } + + + private void initTess() { + PGL.TessellatorCallback callback = new PGL.TessellatorCallback() { + + public void begin(int type) { + // TODO Auto-generated method stub + + } + + public void end() { + // TODO Auto-generated method stub + + } + + public void vertex(Object data) { + if (usedVerts % 3 == 0) { + check(3); + } + + TessVertex vert = (TessVertex)data; + vertexImpl(vert.x, vert.y, vert.u, vert.v, vert.c, vert.f); + } + + public void combine(double[] coords, Object[] data, float[] weights, Object[] outData) { + //here we do some horrible things to blend the colors + float r = 0, g = 0, b = 0, a = 0; + for (int i = 0; i < data.length; ++i) { + int c = ((TessVertex)data[i]).c; + a += weights[i] * ((c >> 24) & 0xFF); + r += weights[i] * ((c >> 16) & 0xFF); + g += weights[i] * ((c >> 8) & 0xFF); + b += weights[i] * (c & 0xFF); + } + int c = ((int)a << 24) + ((int)r << 16) + ((int)g << 8) + (int)b; + + float u = 0, v = 0, f = 0; + for (int i = 0; i < data.length; ++i) { + u += weights[i] * ((TessVertex)data[i]).u; + v += weights[i] * ((TessVertex)data[i]).v; + f += weights[i] * ((TessVertex)data[i]).f; + } + + outData[0] = new TessVertex((float)coords[0], (float)coords[1], u, v, c, f); + } + + public void error(int err) { + println("glu error: " + err); + } + }; + tess = pgl.createTessellator(callback); + + // We specify the edge flag callback as a no-op to force the tesselator to only pass us + // triangle primitives (no triangle fans or triangle strips), for simplicity + tess.setCallback(PGL.TESS_EDGE_FLAG); + tess.setWindingRule(PGL.TESS_WINDING_NONZERO); + } + + + private void initVerts() { + for (int i = 0; i < shapeVerts.length; ++i) { + shapeVerts[i] = new TessVertex(); + } + } + + + private void flushBuffer() { + if (usedVerts == 0) { + return; + } + + if (vbo == 0) { + // Generate vbo + IntBuffer vboBuff = IntBuffer.allocate(1); + pgl.genBuffers(1, vboBuff); + vbo = vboBuff.get(0); + } + + // Upload vertex data + pgl.bindBuffer(PGL.ARRAY_BUFFER, vbo); + pgl.bufferData(PGL.ARRAY_BUFFER, usedVerts * vertSize, + FloatBuffer.wrap(vertexData), PGL.DYNAMIC_DRAW); + + PShader shader = getShader(); + shader.bind(); + setAttribs(); + loadUniforms(); + + pgl.drawArrays(PGL.TRIANGLES, 0, usedVerts); + + usedVerts = 0; + shader.unbind(); + + //XXX: DEBUG +// println("flushed: " + tex + ", " + imageTex); + } + + + private boolean checkShaderLocs(PShader shader) { + int positionLoc = shader.getAttributeLoc("position"); + if (positionLoc == -1) { + positionLoc = shader.getAttributeLoc("vertex"); + } +// int colorLoc = shader.getAttributeLoc("color"); + int transformLoc = shader.getUniformLoc("transform"); + if (transformLoc == -1) { + transformLoc = shader.getUniformLoc("transformMatrix"); + } + + /* + // Became less demanding and 2D shaders do not need to have texture uniforms/attribs + int texScaleLoc = shader.getUniformLoc("texScale"); + if (texScaleLoc == -1) { + texScaleLoc = shader.getUniformLoc("texOffset"); + } + int texCoordLoc = shader.getAttributeLoc("texCoord"); + int texFactorLoc = shader.getAttributeLoc("texFactor"); + */ + + return positionLoc != -1 && transformLoc != -1; +// colorLoc != -1 && texCoordLoc != -1 && texFactorLoc != -1 && texScaleLoc != -1; + } + + + private void loadShaderLocs(PShader shader) { + positionLoc = shader.getAttributeLoc("position"); + if (positionLoc == -1) { + positionLoc = shader.getAttributeLoc("vertex"); + } + colorLoc = shader.getAttributeLoc("color"); + texCoordLoc = shader.getAttributeLoc("texCoord"); + texFactorLoc = shader.getAttributeLoc("texFactor"); + transformLoc = shader.getUniformLoc("transform"); + if (transformLoc == -1) { + transformLoc = shader.getUniformLoc("transformMatrix"); + } + texScaleLoc = shader.getUniformLoc("texScale"); + if (texScaleLoc == -1) { + texScaleLoc = shader.getUniformLoc("texOffset"); + } + } + + + private PShader getShader() { + // TODO: Perhaps a better way to handle the new 2D rendering would be to define a PShader2D + // subclass of PShader... + PShader shader; + if (twoShader == null) { + if (defTwoShader == null) { + String[] vertSource = pgl.loadVertexShader(defP2DShaderVertURL); + String[] fragSource = pgl.loadFragmentShader(defP2DShaderFragURL); + defTwoShader = new PShader(parent, vertSource, fragSource); + } + shader = defTwoShader; + } else { + shader = twoShader; + } +// if (shader != defTwoShader) { + loadShaderLocs(shader); +// } + return shader; + } + + @Override + protected PShader getPolyShader(boolean lit, boolean tex) { + return super.getPolyShader(lit, tex); + } + + private void setAttribs() { + pgl.vertexAttribPointer(positionLoc, 3, PGL.FLOAT, false, vertSize, 0); + pgl.enableVertexAttribArray(positionLoc); + if (-1 < texCoordLoc) { + pgl.vertexAttribPointer(texCoordLoc, 2, PGL.FLOAT, false, vertSize, 3*Float.BYTES); + pgl.enableVertexAttribArray(texCoordLoc); + } + pgl.vertexAttribPointer(colorLoc, 4, PGL.UNSIGNED_BYTE, true, vertSize, 5*Float.BYTES); + pgl.enableVertexAttribArray(colorLoc); + if (-1 < texFactorLoc) { + pgl.vertexAttribPointer(texFactorLoc, 1, PGL.FLOAT, false, vertSize, 6*Float.BYTES); + pgl.enableVertexAttribArray(texFactorLoc); + } + } + + + private void loadUniforms() { + //set matrix uniform + pgl.uniformMatrix4fv(transformLoc, 1, true, FloatBuffer.wrap(new PMatrix3D().get(null))); + + //set texture info + pgl.activeTexture(PGL.TEXTURE0); + pgl.bindTexture(PGL.TEXTURE_2D, tex); + if (-1 < texScaleLoc) { + //enable uv scaling only for use-defined images, not for fonts + if (tex == imageTex) { + pgl.uniform2f(texScaleLoc, 1f/texWidth, 1f/texHeight); + } else { + pgl.uniform2f(texScaleLoc, 1, 1); + } + } + } + + + private void textureImpl(int glId) { + if (glId == tex) { + return; //texture is already bound; no work to be done + } + + flushBuffer(); + tex = glId; + } + + + private void check(int newVerts) { + if (usedVerts + newVerts > maxVerts) { + flushBuffer(); + } + } + + + private void vertexImpl(float x, float y, float u, float v, int c, float f) { + int idx = usedVerts * 7; + //inline multiply only x and y to avoid an allocation and a few flops + vertexData[idx + 0] = projmodelview.m00*x + projmodelview.m01*y + projmodelview.m03; + vertexData[idx + 1] = projmodelview.m10*x + projmodelview.m11*y + projmodelview.m13; + vertexData[idx + 2] = depth; + vertexData[idx + 3] = u; + vertexData[idx + 4] = v; + vertexData[idx + 5] = Float.intBitsToFloat(c); + vertexData[idx + 6] = f; + usedVerts++; + } + + + private void vertexImpl(TessVertex vert) { + vertexImpl(vert.x, vert.y, vert.u, vert.v, vert.c, vert.f); + } + + + //one of POINTS, LINES, TRIANGLES, TRIANGLE_FAN, TRIANGLE_STRIP, QUADS, QUAD_STRIP, POLYGON + private int shapeType; + private int vertCount; + private TessVertex[] shapeVerts = new TessVertex[16]; //initial size is arbitrary + + //list of indices (into shapeVerts array) at which a new contour begins + private int[] contours = new int[2]; //initial size is arbitrary + private int contourCount; + + + private void appendContour(int vertIndex) { + //dynamically expand contour array as needed + if (contourCount >= contours.length) { + contours = PApplet.expand(contours, contours.length * 2); + } + + contours[contourCount] = vertIndex; + contourCount += 1; + } + + + //used by endShape() as a temporary to avoid unnecessary allocations + private double[] tempDoubles = new double[3]; + + //If this flag is set, then the next call to endShape() with shape type of POLYGON + //will triangulate blindly instead of going through the GLU tessellator (for performance). + //This is useful for shapes (like ellipse(), rect(), etc.) that we know will always be convex. + //TODO: Make this an optional argument to endShape() + //once we start integrating PGraphics4D into the rest of the codebase. + private boolean knownConvexPolygon = false; + + + private void shapeVertex(float x, float y, float u, float v, int c, float f) { + //avoid adding a duplicate because it will cause the GLU tess to fail spectacularly + //by spitting out-of-memory errors and passing null parameters to the combine() callback + //TODO: figure out why that happens and how to stop it + //(P2D renderer doesn't appear to have such a problem, so presumably there must be a way) + for (int i = 0; i < vertCount; ++i) { + if (shapeVerts[i].x == x && shapeVerts[i].y == y) { + return; + } + } + + //dynamically expand input vertex array as needed + if (vertCount >= shapeVerts.length) { + shapeVerts = (TessVertex[]) PApplet.expand(shapeVerts, shapeVerts.length * 2); + + //allocate objects for the new half of the array so we don't NPE ourselves + for (int i = shapeVerts.length/2; i < shapeVerts.length; ++i) { + shapeVerts[i] = new TessVertex(); + } + } + + shapeVerts[vertCount].set(x, y, u, v, c, f); + vertCount += 1; + } + + + private void triangle(float x1, float y1, float x2, float y2, float x3, float y3, int color) { + check(3); + vertexImpl(x1, y1, 0, 0, color, 0); + vertexImpl(x2, y2, 0, 0, color, 0); + vertexImpl(x3, y3, 0, 0, color, 0); + } + + + //below r == LINE_DETAIL_LIMIT, all lines will be drawn as plain rectangles + //instead of using fancy stroke rendering algorithms, since the result is visually indistinguishable + static final private float LINE_DETAIL_LIMIT = 1.0f; + + + private void singleLine(float x1, float y1, float x2, float y2, int color) { + float r = strokeWeight * 0.5f; + + float dx = x2 - x1; + float dy = y2 - y1; + float d = PApplet.sqrt(dx*dx + dy*dy); + float tx = dy / d * r; + float ty = dx / d * r; + + if (strokeCap == PROJECT) { + x1 -= ty; + x2 += ty; + y1 -= tx; + y2 += tx; + } + + triangle(x1 - tx, y1 + ty, x1 + tx, y1 - ty, x2 - tx, y2 + ty, color); + triangle(x2 + tx, y2 - ty, x2 - tx, y2 + ty, x1 + tx, y1 - ty, color); + + if (r >= LINE_DETAIL_LIMIT && strokeCap == ROUND) { + int segments = circleDetail(r, HALF_PI); + float step = HALF_PI / segments; + float c = PApplet.cos(step); + float s = PApplet.sin(step); + for (int i = 0; i < segments; ++i) { + //this is the equivalent of multiplying the vector by the 2x2 rotation matrix [[c -s] [s c]] + float nx = c * tx - s * ty; + float ny = s * tx + c * ty; + + triangle(x2, y2, x2 + ty, y2 + tx, x2 + ny, y2 + nx, color); + triangle(x2, y2, x2 - tx, y2 + ty, x2 - nx, y2 + ny, color); + triangle(x1, y1, x1 - ty, y1 - tx, x1 - ny, y1 - nx, color); + triangle(x1, y1, x1 + tx, y1 - ty, x1 + nx, y1 - ny, color); + + tx = nx; + ty = ny; + } + } + } + + + private void singlePoint(float x, float y, int color) { + float r = strokeWeight * 0.5f; + if (r >= LINE_DETAIL_LIMIT && strokeCap == ROUND) { + int segments = circleDetail(r); + float step = QUARTER_PI / segments; + + float x1 = 0, y1 = r; + float c = PApplet.cos(step); + float s = PApplet.sin(step); + for (int i = 0; i < segments; ++i) { + //this is the equivalent of multiplying the vector by the 2x2 rotation matrix [[c -s] [s c]] + float x2 = c * x1 - s * y1; + float y2 = s * x1 + c * y1; + + triangle(x, y, x + x1, y + y1, x + x2, y + y2, strokeColor); + triangle(x, y, x + x1, y - y1, x + x2, y - y2, strokeColor); + triangle(x, y, x - x1, y + y1, x - x2, y + y2, strokeColor); + triangle(x, y, x - x1, y - y1, x - x2, y - y2, strokeColor); + + triangle(x, y, x + y1, y + x1, x + y2, y + x2, strokeColor); + triangle(x, y, x + y1, y - x1, x + y2, y - x2, strokeColor); + triangle(x, y, x - y1, y + x1, x - y2, y + x2, strokeColor); + triangle(x, y, x - y1, y - x1, x - y2, y - x2, strokeColor); + + x1 = x2; + y1 = y2; + } + } else { + triangle(x - r, y - r, x + r, y - r, x - r, y + r, color); + triangle(x + r, y - r, x - r, y + r, x + r, y + r, color); + } + } + + + private StrokeRenderer sr = new StrokeRenderer(); + + + private class StrokeRenderer { + int lineVertexCount; + float fx, fy; + float sx, sy, sdx, sdy; + float px, py, pdx, pdy; + float lx, ly; + float r; + + + void arcJoin(float x, float y, float dx1, float dy1, float dx2, float dy2) { + //we don't need to normalize before doing these products + //since the vectors are the same length and only used as arguments to atan2() + float cross = dx1 * dy2 - dy1 * dx2; + float dot = dx1 * dx2 + dy1 * dy2; + float theta = PApplet.atan2(cross, dot); + int segments = circleDetail(r, theta); + float px = x + dx1, py = y + dy1; + if (segments > 1) { + float c = PApplet.cos(theta / segments); + float s = PApplet.sin(theta / segments); + for (int i = 1; i < segments; ++i) { + //this is the equivalent of multiplying the vector by the 2x2 rotation matrix [[c -s] [s c]] + float tempx = c * dx1 - s * dy1; + dy1 = s * dx1 + c * dy1; + dx1 = tempx; + + float nx = x + dx1; + float ny = y + dy1; + triangle(x, y, px, py, nx, ny, strokeColor); + px = nx; + py = ny; + } + } + triangle(x, y, px, py, x + dx2, y + dy2, strokeColor); + } + + void beginLine() { + lineVertexCount = 0; + r = strokeWeight * 0.5f; + } + + void lineVertex(float x, float y) { + //disallow adding consecutive duplicate vertices, + //as it is pointless and just creates an extra edge case + if (lineVertexCount > 0 && x == lx && y == ly) { + return; + } + + if (lineVertexCount == 0) { + fx = x; + fy = y; + } else if (r < LINE_DETAIL_LIMIT) { + singleLine(lx, ly, x, y, strokeColor); + } else if (lineVertexCount == 1) { + sx = x; + sy = y; + } else { + //calculate normalized direction vectors for each leg + float leg1x = lx - px; + float leg1y = ly - py; + float leg2x = x - lx; + float leg2y = y - ly; + float len1 = PApplet.sqrt(leg1x * leg1x + leg1y * leg1y); + float len2 = PApplet.sqrt(leg2x * leg2x + leg2y * leg2y); + leg1x /= len1; + leg1y /= len1; + leg2x /= len2; + leg2y /= len2; + + float legDot = -leg1x * leg2x - leg1y * leg2y; + float cosPiOver15 = 0.97815f; + if (strokeJoin == BEVEL || strokeJoin == ROUND || legDot > cosPiOver15 || legDot < -0.999) { + float tx = leg1y * r; + float ty = -leg1x * r; + + if (lineVertexCount == 2) { + sdx = tx; + sdy = ty; + } else { + triangle(px - pdx, py - pdy, px + pdx, py + pdy, lx - tx, ly - ty, strokeColor); + triangle(px + pdx, py + pdy, lx - tx, ly - ty, lx + tx, ly + ty, strokeColor); + } + + float nx = leg2y * r; + float ny = -leg2x * r; + + float legCross = leg1x * leg2y - leg1y * leg2x; + if (strokeJoin == ROUND) { + if (legCross > 0) { + arcJoin(lx, ly, tx, ty, nx, ny); + } else { + arcJoin(lx, ly, -tx, -ty, -nx, -ny); + } + } else if (legCross > 0) { + triangle(lx, ly, lx + tx, ly + ty, lx + nx, ly + ny, strokeColor); + } else { + triangle(lx, ly, lx - tx, ly - ty, lx - nx, ly - ny, strokeColor); + } + + pdx = nx; + pdy = ny; + } else { //miter joint + //find the bisecting vector + float x1 = leg2x - leg1x; + float y1 = leg2y - leg1y; + //find a (normalized) vector perpendicular to one of the legs + float x2 = leg1y; + float y2 = -leg1x; + //scale the bisecting vector to the correct length using magic (not sure how to explain this one) + float dot = x1 * x2 + y1 * y2; + float bx = x1 * (r / dot); + float by = y1 * (r / dot); + + if (lineVertexCount == 2) { + sdx = bx; + sdy = by; + } else { + triangle(px - pdx, py - pdy, px + pdx, py + pdy, lx - bx, ly - by, strokeColor); + triangle(px + pdx, py + pdy, lx - bx, ly - by, lx + bx, ly + by, strokeColor); + } + + pdx = bx; + pdy = by; + } + } + + px = lx; + py = ly; + lx = x; + ly = y; + + lineVertexCount += 1; + } + + void lineCap(float x, float y, float dx, float dy) { + int segments = circleDetail(r, HALF_PI); + float px = dy, py = -dx; + if (segments > 1) { + float c = PApplet.cos(HALF_PI / segments); + float s = PApplet.sin(HALF_PI / segments); + for (int i = 1; i < segments; ++i) { + //this is the equivalent of multiplying the vector by the 2x2 rotation matrix [[c -s] [s c]] + float nx = c * px - s * py; + float ny = s * px + c * py; + triangle(x, y, x + px, y + py, x + nx, y + ny, strokeColor); + triangle(x, y, x - py, y + px, x - ny, y + nx, strokeColor); + px = nx; + py = ny; + } + } + triangle(x, y, x + px, y + py, x + dx, y + dy, strokeColor); + triangle(x, y, x - py, y + px, x - dy, y + dx, strokeColor); + } + + void endLine(boolean closed) { + if (lineVertexCount < 2) { + return; + } + + if (lineVertexCount == 2) { + singleLine(px, py, lx, ly, strokeColor); + return; + } + + if (r < LINE_DETAIL_LIMIT) { + if (closed) { + singleLine(lx, ly, fx, fy, strokeColor); + } + return; + } + + if (closed) { + //draw the last two legs + lineVertex(fx, fy); + lineVertex(sx, sy); + + //connect first and second vertices + triangle(px - pdx, py - pdy, px + pdx, py + pdy, sx - sdx, sy - sdy, strokeColor); + triangle(px + pdx, py + pdy, sx - sdx, sy - sdy, sx + sdx, sy + sdy, strokeColor); + } else { + //draw last line (with cap) + float dx = lx - px; + float dy = ly - py; + float d = PApplet.sqrt(dx*dx + dy*dy); + float tx = dy / d * r; + float ty = -dx / d * r; + + if (strokeCap == PROJECT) { + lx -= ty; + ly += tx; + } + + triangle(px - pdx, py - pdy, px + pdx, py + pdy, lx - tx, ly - ty, strokeColor); + triangle(px + pdx, py + pdy, lx - tx, ly - ty, lx + tx, ly + ty, strokeColor); + + if (strokeCap == ROUND) { + lineCap(lx, ly, -ty, tx); + } + + //draw first line (with cap) + dx = fx - sx; + dy = fy - sy; + d = PApplet.sqrt(dx*dx + dy*dy); + tx = dy / d * r; + ty = -dx / d * r; + + if (strokeCap == PROJECT) { + fx -= ty; + fy += tx; + } + + triangle(sx - sdx, sy - sdy, sx + sdx, sy + sdy, fx + tx, fy + ty, strokeColor); + triangle(sx + sdx, sy + sdy, fx + tx, fy + ty, fx - tx, fy - ty, strokeColor); + + if (strokeCap == ROUND) { + lineCap(fx, fy, -ty, tx); + } + } + } + } + + + //returns the total number of points needed to approximate an arc of a given radius and extent + int circleDetail(float radius, float delta) { + //this serves as a rough approximation of how much the longest axis + //of an ellipse will be scaled by a given matrix + //(in other words, the amount by which its on-screen size changes) + float sxi = projmodelview.m00 * width / 2; + float syi = projmodelview.m10 * height / 2; + float sxj = projmodelview.m01 * width / 2; + float syj = projmodelview.m11 * height / 2; + float Imag2 = sxi * sxi + syi * syi; + float Jmag2 = sxj * sxj + syj * syj; + float ellipseDetailMultiplier = PApplet.sqrt(PApplet.max(Imag2, Jmag2)); + radius *= ellipseDetailMultiplier; + return (int)(PApplet.min(127, PApplet.sqrt(radius) / QUARTER_PI * PApplet.abs(delta) * 0.75f) + 1); + } + + + //returns the number of points per quadrant needed to approximate a circle of a given radius + int circleDetail(float radius) { + return circleDetail(radius, QUARTER_PI); + } + + + private class TessVertex { + float x, y, u, v; + int c; + float f; //1.0 if textured, 0.0 if flat + + public TessVertex() { + //no-op + } + + public TessVertex(float x, float y, float u, float v, int c, float f) { + set(x, y, u, v, c, f); + } + + public void set(float x, float y, float u, float v, int c, float f) { + this.x = x; + this.y = y; + this.u = u; + this.v = v; + this.c = c; + this.f = f; + } + + @Override + public String toString() { + return x + ", " + y; + } + } +} diff --git a/core/src/processing/opengl/PGraphics3D.java b/libs/processing-core/src/main/java/processing/opengl/PGraphics3D.java similarity index 85% rename from core/src/processing/opengl/PGraphics3D.java rename to libs/processing-core/src/main/java/processing/opengl/PGraphics3D.java index 77edcc9f0..5a5e1c3b3 100644 --- a/core/src/processing/opengl/PGraphics3D.java +++ b/libs/processing-core/src/main/java/processing/opengl/PGraphics3D.java @@ -3,11 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2012 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -18,19 +20,15 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ package processing.opengl; -import java.io.InputStream; -import java.util.zip.GZIPInputStream; - -import processing.core.PApplet; -import processing.core.PConstants; import processing.core.PGraphics; import processing.core.PShape; import processing.core.PShapeOBJ; + public class PGraphics3D extends PGraphicsOpenGL { public PGraphics3D() { @@ -85,12 +83,12 @@ protected void defaultCamera() { @Override protected void begin2D() { pushProjection(); - ortho(0, width, 0, height, -1, +1); + ortho(-width/2f, width/2f, -height/2f, height/2f); pushMatrix(); // Set camera for 2D rendering, it simply centers at (width/2, height/2) - float centerX = width/2; - float centerY = height/2; + float centerX = width/2f; + float centerY = height/2f; modelview.reset(); modelview.translate(-centerX, -centerY); @@ -111,6 +109,7 @@ protected void end2D() { } + ////////////////////////////////////////////////////////////// // SHAPE I/O @@ -127,37 +126,39 @@ static protected PShape loadShapeImpl(PGraphics pg, String filename, if (extension.equals("obj")) { obj = new PShapeOBJ(pg.parent, filename); - - } else if (extension.equals("objz")) { - try { - // TODO: The obj file can be read from the gzip, but if it refers to - // a materials file and texture images, those must be contained in the - // data folder, cannot be inside the gzip. - InputStream input = - new GZIPInputStream(pg.parent.createInput(filename)); - obj = new PShapeOBJ(pg.parent, PApplet.createReader(input)); - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (obj != null) { int prevTextureMode = pg.textureMode; pg.textureMode = NORMAL; - PShapeOpenGL p3d = PShapeOpenGL.createShape3D((PGraphicsOpenGL)pg, obj); + PShapeOpenGL p3d = PShapeOpenGL.createShape((PGraphicsOpenGL)pg, obj); pg.textureMode = prevTextureMode; return p3d; - } else { - return null; } + return null; } + ////////////////////////////////////////////////////////////// // SHAPE CREATION +// @Override +// protected PShape createShapeFamily(int type) { +// PShape shape = new PShapeOpenGL(this, type); +// shape.set3D(true); +// return shape; +// } +// +// +// @Override +// protected PShape createShapePrimitive(int kind, float... p) { +// PShape shape = new PShapeOpenGL(this, kind, p); +// shape.set3D(true); +// return shape; +// } + + + /* @Override public PShape createShape(PShape source) { return PShapeOpenGL.createShape3D(this, source); @@ -191,7 +192,7 @@ static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, int type) { } else if (type == PShape.GEOMETRY) { shape = new PShapeOpenGL(pg, PShape.GEOMETRY); } - shape.is3D(true); + shape.set3D(true); return shape; } @@ -250,6 +251,7 @@ static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, } shape = new PShapeOpenGL(pg, PShape.PRIMITIVE); shape.setKind(ARC); + } else if (kind == BOX) { if (len != 1 && len != 3) { showWarning("Wrong number of parameters"); @@ -272,7 +274,8 @@ static protected PShapeOpenGL createShapeImpl(PGraphicsOpenGL pg, shape.setParams(p); } - shape.is3D(true); + shape.set3D(true); return shape; } + */ } \ No newline at end of file diff --git a/core/src/processing/opengl/PGraphicsOpenGL.java b/libs/processing-core/src/main/java/processing/opengl/PGraphicsOpenGL.java similarity index 70% rename from core/src/processing/opengl/PGraphicsOpenGL.java rename to libs/processing-core/src/main/java/processing/opengl/PGraphicsOpenGL.java index fa8190ea9..478a2f13b 100644 --- a/core/src/processing/opengl/PGraphicsOpenGL.java +++ b/libs/processing-core/src/main/java/processing/opengl/PGraphicsOpenGL.java @@ -3,11 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2004-13 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License version 2.1 as published by the Free Software Foundation. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -18,32 +20,82 @@ Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ +*/ package processing.opengl; +import processing.android.AppComponent; import processing.core.*; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; import java.net.URL; import java.nio.*; import java.util.*; +import android.content.Context; +import android.os.Environment; +import android.view.SurfaceHolder; + +import static android.os.Environment.isExternalStorageRemovable; + /** * OpenGL renderer. - * */ public class PGraphicsOpenGL extends PGraphics { /** Interface between Processing and OpenGL */ public PGL pgl; /** The renderer currently in use. */ - protected PGraphicsOpenGL currentPG; + public PGraphicsOpenGL currentPG; /** Font cache for texture objects. */ protected WeakHashMap fontMap; // ........................................................ + // Disposal of native resources + // Using the technique alternative to finalization described in: + // http://www.oracle.com/technetwork/articles/java/finalization-137655.html + private static ReferenceQueue refQueue = new ReferenceQueue<>(); + private static List> reachableWeakReferences = + new LinkedList<>(); + + static final private int MAX_DRAIN_GLRES_ITERATIONS = 10; + + static void drainRefQueueBounded() { + int iterations = 0; + while (iterations < MAX_DRAIN_GLRES_ITERATIONS) { + Disposable res = + (Disposable) refQueue.poll(); + if (res == null) { + break; + } + res.dispose(); + ++iterations; + } + } + + private static abstract class Disposable extends WeakReference { + protected Disposable(T obj) { + super(obj, refQueue); + drainRefQueueBounded(); + reachableWeakReferences.add(this); + } + + public void dispose() { + reachableWeakReferences.remove(this); + disposeNative(); + } + + abstract public void disposeNative(); + } + // Basic rendering parameters: /** Whether the PGraphics object is ready to render or not. */ @@ -64,36 +116,40 @@ public class PGraphicsOpenGL extends PGraphics { /** Current flush mode. */ protected int flushMode = FLUSH_WHEN_FULL; + // ........................................................ // VBOs for immediate rendering: - public int glPolyVertex; - public int glPolyColor; - public int glPolyNormal; - public int glPolyTexcoord; - public int glPolyAmbient; - public int glPolySpecular; - public int glPolyEmissive; - public int glPolyShininess; - public int glPolyIndex; + protected VertexBuffer bufPolyVertex; + protected VertexBuffer bufPolyColor; + protected VertexBuffer bufPolyNormal; + protected VertexBuffer bufPolyTexcoord; + protected VertexBuffer bufPolyAmbient; + protected VertexBuffer bufPolySpecular; + protected VertexBuffer bufPolyEmissive; + protected VertexBuffer bufPolyShininess; + protected VertexBuffer bufPolyIndex; protected boolean polyBuffersCreated = false; protected int polyBuffersContext; - public int glLineVertex; - public int glLineColor; - public int glLineAttrib; - public int glLineIndex; + protected VertexBuffer bufLineVertex; + protected VertexBuffer bufLineColor; + protected VertexBuffer bufLineAttrib; + protected VertexBuffer bufLineIndex; protected boolean lineBuffersCreated = false; protected int lineBuffersContext; - public int glPointVertex; - public int glPointColor; - public int glPointAttrib; - public int glPointIndex; + protected VertexBuffer bufPointVertex; + protected VertexBuffer bufPointColor; + protected VertexBuffer bufPointAttrib; + protected VertexBuffer bufPointIndex; protected boolean pointBuffersCreated = false; protected int pointBuffersContext; + // Generic vertex attributes (only for polys) + protected AttributeMap polyAttribs; + static protected final int INIT_VERTEX_BUFFER_SIZE = 256; static protected final int INIT_INDEX_BUFFER_SIZE = 512; @@ -110,6 +166,8 @@ public class PGraphicsOpenGL extends PGraphics { static public boolean packedDepthStencilSupported; static public boolean anisoSamplingSupported; static public boolean blendEqSupported; + static public boolean readBufferSupported; + static public boolean drawBufferSupported; /** Some hardware limits */ static public int maxTextureSize; @@ -127,54 +185,35 @@ public class PGraphicsOpenGL extends PGraphics { // ........................................................ - // GL resources: - - static protected HashMap glTextureObjects = - new HashMap(); - static protected HashMap glVertexBuffers = - new HashMap(); - static protected HashMap glFrameBuffers = - new HashMap(); - static protected HashMap glRenderBuffers = - new HashMap(); - static protected HashMap glslPrograms = - new HashMap(); - static protected HashMap glslVertexShaders = - new HashMap(); - static protected HashMap glslFragmentShaders = - new HashMap(); - - // ........................................................ - // Shaders static protected URL defColorShaderVertURL = - PGraphicsOpenGL.class.getResource("ColorVert.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/ColorVert.glsl"); static protected URL defTextureShaderVertURL = - PGraphicsOpenGL.class.getResource("TextureVert.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/TexVert.glsl"); static protected URL defLightShaderVertURL = - PGraphicsOpenGL.class.getResource("LightVert.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/LightVert.glsl"); static protected URL defTexlightShaderVertURL = - PGraphicsOpenGL.class.getResource("TexlightVert.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/TexLightVert.glsl"); static protected URL defColorShaderFragURL = - PGraphicsOpenGL.class.getResource("ColorFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/ColorFrag.glsl"); static protected URL defTextureShaderFragURL = - PGraphicsOpenGL.class.getResource("TextureFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/TexFrag.glsl"); static protected URL defLightShaderFragURL = - PGraphicsOpenGL.class.getResource("LightFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/LightFrag.glsl"); static protected URL defTexlightShaderFragURL = - PGraphicsOpenGL.class.getResource("TexlightFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/TexLightFrag.glsl"); static protected URL defLineShaderVertURL = - PGraphicsOpenGL.class.getResource("LineVert.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/LineVert.glsl"); static protected URL defLineShaderFragURL = - PGraphicsOpenGL.class.getResource("LineFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/LineFrag.glsl"); static protected URL defPointShaderVertURL = - PGraphicsOpenGL.class.getResource("PointVert.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/PointVert.glsl"); static protected URL defPointShaderFragURL = - PGraphicsOpenGL.class.getResource("PointFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/PointFrag.glsl"); static protected URL maskShaderFragURL = - PGraphicsOpenGL.class.getResource("MaskFrag.glsl"); + PGraphicsOpenGL.class.getResource("/assets/shaders/MaskFrag.glsl"); protected PShader defColorShader; protected PShader defTextureShader; @@ -195,7 +234,30 @@ public class PGraphicsOpenGL extends PGraphics { protected InGeometry inGeo; protected TessGeometry tessGeo; protected TexCache texCache; - static protected Tessellator tessellator; + protected Tessellator tessellator; + + // ........................................................ + + // Depth sorter + + protected DepthSorter sorter; + protected boolean isDepthSortingEnabled; + + // ........................................................ + + // Async pixel reader + + protected AsyncPixelReader asyncPixelReader; + protected boolean asyncPixelReaderInitialized; + + // Keeps track of ongoing transfers so they can be finished. + // Set is copied to the List when we need to iterate it + // so that readers can remove themselves from the Set during + // iteration if they don't have any ongoing transfers. + protected static final Set + ongoingPixelTransfers = new HashSet<>(); + protected static final List + ongoingPixelTransfersIterable = new ArrayList<>(); // ........................................................ @@ -211,12 +273,24 @@ public class PGraphicsOpenGL extends PGraphics { /** Aspect ratio of camera's view. */ public float cameraAspect; - /** Actual position of the camera. */ - protected float cameraEyeX, cameraEyeY, cameraEyeZ; + /** Default camera properties. */ + public float defCameraFOV; + public float defCameraX, defCameraY, defCameraZ; + public float defCameraNear, defCameraFar; + public float defCameraAspect; + + /** Distance between camera eye and center. */ + protected float eyeDist; /** Flag to indicate that we are inside beginCamera/endCamera block. */ protected boolean manipulatingCamera; + /** + * Sets the coordinates to "first person" setting: Y axis up, origin at + * screen center + */ + protected boolean cameraUp = false; + // ........................................................ // All the matrices required for camera and geometry transformations. @@ -236,14 +310,17 @@ public class PGraphicsOpenGL extends PGraphics { // Useful to have around. static protected PMatrix3D identity = new PMatrix3D(); - protected boolean matricesAllocated = false; - /** * Marks when changes to the size have occurred, so that the camera * will be reset in beginDraw(). */ protected boolean sized; + /** + * Marks when some changes have occurred, to the surface view. + */ + protected boolean changed; + static protected final int MATRIX_STACK_DEPTH = 32; protected int modelviewStackDepth; @@ -264,6 +341,18 @@ public class PGraphicsOpenGL extends PGraphics { /** Projection matrix stack **/ protected float[][] projectionStack = new float[MATRIX_STACK_DEPTH][16]; + + /** Matrix that transform coordinates to the eye coordinate system **/ + protected PMatrix3D eyeMatrix; + + /** Matrix that transform coordinates to the system that results from appling the modelview transformation **/ + protected PMatrix3D objMatrix; + + /** Vectors defining the eye axes **/ + public float forwardX, forwardY, forwardZ; + public float rightX, rightY, rightZ; + public float upX, upY, upZ; + // ........................................................ // Lights: @@ -311,8 +400,6 @@ public class PGraphicsOpenGL extends PGraphics { public float currentLightFalloffLinear; public float currentLightFalloffQuadratic; - protected boolean lightsAllocated = false; - // ........................................................ // Texturing: @@ -364,10 +451,10 @@ public class PGraphicsOpenGL extends PGraphics { // Screen surface: /** Texture containing the current frame */ - protected Texture texture; + protected Texture texture = null; /** Texture containing the previous frame */ - protected Texture ptexture; + protected Texture ptexture = null; /** IntBuffer wrapping the pixels array. */ protected IntBuffer pixelBuffer; @@ -379,15 +466,11 @@ public class PGraphicsOpenGL extends PGraphics { protected IntBuffer nativePixelBuffer; /** texture used to apply a filter on the screen image. */ - protected Texture filterTexture; + protected Texture filterTexture = null; /** PImage that wraps filterTexture. */ protected PImage filterImage; - /** Flag to indicate that pixels array is up-to-date and - * ready to be manipulated through the set()/get() methods */ - protected boolean arePixelsUpToDate; - // ........................................................ // Utility variables: @@ -395,9 +478,6 @@ public class PGraphicsOpenGL extends PGraphics { /** True if we are inside a beginDraw()/endDraw() block. */ protected boolean drawing = false; - /** Used to indicate an OpenGL surface recreation */ - protected boolean restoreSurface = false; - /** Used to detect continuous use of the smooth/noSmooth functions */ protected boolean smoothDisabled = false; protected int smoothCallCount = 0; @@ -416,10 +496,6 @@ public class PGraphicsOpenGL extends PGraphics { /** Viewport dimensions. */ protected IntBuffer viewport; - /** Used to register calls to glClear. */ - protected boolean clearColorBuffer; - protected boolean clearColorBuffer0; - protected boolean openContour = false; protected boolean breakShape = false; protected boolean defaultEdges = false; @@ -456,6 +532,21 @@ public class PGraphicsOpenGL extends PGraphics { // ........................................................ + // Variables used in ray casting: + + protected PVector[] ray; + protected PVector hit = new PVector(); + protected PVector screen = new PVector(); + + protected PVector origInObjCoord = new PVector(); + protected PVector hitInObjCoord = new PVector(); + protected PVector dirInObjCoord = new PVector(); + + protected PVector origInWorldCoord = new PVector(); + protected PVector dirInWorldCoord = new PVector(); + + // ........................................................ + // Error strings: static final String OPENGL_THREAD_ERROR = @@ -509,10 +600,14 @@ public class PGraphicsOpenGL extends PGraphics { static final String NO_COLOR_SHADER_ERROR = "Your shader needs to be of COLOR type " + "to render this geometry properly, using default shader instead."; - static final String TOO_LONG_STROKE_PATH_ERROR = - "Stroke path is too long, some bevel triangles won't be added"; static final String TESSELLATION_ERROR = "Tessellation Error: %1$s"; + static final String GL_THREAD_NOT_CURRENT = + "You are trying to draw outside OpenGL's animation thread.\n" + + "Place all drawing commands in the draw() function, or inside\n" + + "your own functions as long as they are called from draw(),\n" + + "but not in event handling functions such as keyPressed()\n" + + "or mousePressed()."; ////////////////////////////////////////////////////////////// @@ -522,10 +617,6 @@ public class PGraphicsOpenGL extends PGraphics { public PGraphicsOpenGL() { pgl = createPGL(this); - if (tessellator == null) { - tessellator = new Tessellator(); - } - if (intBuffer == null) { intBuffer = PGL.allocateIntBuffer(2); floatBuffer = PGL.allocateFloatBuffer(2); @@ -533,14 +624,41 @@ public PGraphicsOpenGL() { viewport = PGL.allocateIntBuffer(4); - inGeo = newInGeometry(this, IMMEDIATE); - tessGeo = newTessGeometry(this, IMMEDIATE); + polyAttribs = newAttributeMap(); + inGeo = newInGeometry(this, polyAttribs, IMMEDIATE); + tessGeo = newTessGeometry(this, polyAttribs, IMMEDIATE); texCache = newTexCache(this); + projection = new PMatrix3D(); + camera = new PMatrix3D(); + cameraInv = new PMatrix3D(); + modelview = new PMatrix3D(); + modelviewInv = new PMatrix3D(); + projmodelview = new PMatrix3D(); + + lightType = new int[PGL.MAX_LIGHTS]; + lightPosition = new float[4 * PGL.MAX_LIGHTS]; + lightNormal = new float[3 * PGL.MAX_LIGHTS]; + lightAmbient = new float[3 * PGL.MAX_LIGHTS]; + lightDiffuse = new float[3 * PGL.MAX_LIGHTS]; + lightSpecular = new float[3 * PGL.MAX_LIGHTS]; + lightFalloffCoefficients = new float[3 * PGL.MAX_LIGHTS]; + lightSpotParameters = new float[2 * PGL.MAX_LIGHTS]; + currentLightSpecular = new float[3]; + initialized = false; } + @Override + public void setParent(PApplet parent) { + super.setParent(parent); + if (pgl != null) { + pgl.sketch = parent; + } + } + + @Override public void setPrimary(boolean primary) { super.setPrimary(primary); @@ -549,6 +667,9 @@ public void setPrimary(boolean primary) { if (primary) { fbStack = new FrameBuffer[FB_STACK_DEPTH]; fontMap = new WeakHashMap(); + tessellator = new Tessellator(); + } else { + tessellator = getPrimaryPG().tessellator; } } @@ -560,654 +681,779 @@ public void setPrimary(boolean primary) { @Override - public void setFrameRate(float frameRate) { - pgl.setFps(frameRate); + public void surfaceChanged() { + changed = true; } @Override - public void setSize(int iwidth, int iheight) { - width = iwidth; - height = iheight; - - allocate(); - - // init perspective projection based on new dimensions - cameraFOV = 60 * DEG_TO_RAD; // at least for now - cameraX = width / 2.0f; - cameraY = height / 2.0f; - cameraZ = cameraY / ((float) Math.tan(cameraFOV / 2.0f)); - cameraNear = cameraZ / 10.0f; - cameraFar = cameraZ * 10.0f; - cameraAspect = (float) width / (float) height; - - sized = true; + public void reset() { + pgl.resetFBOLayer(); + restartPGL(); } - /** - * Called by resize(), this handles creating the actual GLCanvas the - * first time around, or simply resizing it on subsequent calls. - * There is no pixel array to allocate for an OpenGL canvas - * because OpenGL's pixel buffer is all handled internally. - */ @Override - protected void allocate() { - super.allocate(); + public void setSize(int iwidth, int iheight) { + sized = iwidth != width || iheight != height; + super.setSize(iwidth, iheight); - if (!matricesAllocated) { - projection = new PMatrix3D(); - camera = new PMatrix3D(); - cameraInv = new PMatrix3D(); - modelview = new PMatrix3D(); - modelviewInv = new PMatrix3D(); - projmodelview = new PMatrix3D(); - matricesAllocated = true; - } + updatePixelSize(); - if (!lightsAllocated) { - lightType = new int[PGL.MAX_LIGHTS]; - lightPosition = new float[4 * PGL.MAX_LIGHTS]; - lightNormal = new float[3 * PGL.MAX_LIGHTS]; - lightAmbient = new float[3 * PGL.MAX_LIGHTS]; - lightDiffuse = new float[3 * PGL.MAX_LIGHTS]; - lightSpecular = new float[3 * PGL.MAX_LIGHTS]; - lightFalloffCoefficients = new float[3 * PGL.MAX_LIGHTS]; - lightSpotParameters = new float[2 * PGL.MAX_LIGHTS]; - currentLightSpecular = new float[3]; - lightsAllocated = true; - } + // init perspective projection based on new dimensions + defCameraFOV = 60 * DEG_TO_RAD; // at least for now + defCameraX = width / 2.0f; + defCameraY = height / 2.0f; + defCameraZ = defCameraY / ((float) Math.tan(defCameraFOV / 2.0f)); + defCameraNear = defCameraZ / 10.0f; + defCameraFar = defCameraZ * 10.0f; + defCameraAspect = (float) width / (float) height; + + cameraFOV = defCameraFOV; + cameraX = defCameraX; + cameraY = defCameraY; + cameraZ = defCameraZ; + cameraNear = defCameraNear; + cameraFar = defCameraFar; + cameraAspect = defCameraAspect; } @Override public void dispose() { // PGraphics - super.dispose(); - - if (primarySurface) { - // Swap buffers the end to make sure that no - // garbage is shown on the screen, this particularly - // affects non-interactive sketches on windows that - // render only 1 frame, so no enough rendering - // iterations have been conducted so far to properly - // initialize all the buffers. - pgl.swapBuffers(); + if (asyncPixelReader != null) { + asyncPixelReader.dispose(); + asyncPixelReader = null; } - finalizePolyBuffers(); - finalizeLineBuffers(); - finalizePointBuffers(); - - deleteSurfaceTextures(); - if (primarySurface) { - deleteDefaultShaders(); - } else { - if (offscreenFramebuffer != null) { - offscreenFramebuffer.dispose(); + if (!primaryGraphics) { + deleteSurfaceTextures(); + FrameBuffer ofb = offscreenFramebuffer; + FrameBuffer mfb = multisampleFramebuffer; + if (ofb != null) { + ofb.dispose(); } - if (multisampleFramebuffer != null) { - multisampleFramebuffer.dispose(); + if (mfb != null) { + mfb.dispose(); } } - deleteFinalizedGLResources(pgl); + pgl.dispose(); - if (primarySurface) { - pgl.deleteSurface(); - } + super.dispose(); } - @Override - protected void finalize() throws Throwable { - try { - finalizePolyBuffers(); - finalizeLineBuffers(); - finalizePointBuffers(); - - deleteSurfaceTextures(); - if (!primarySurface) { - if (offscreenFramebuffer != null) { - offscreenFramebuffer.dispose(); - offscreenFramebuffer = null; - } - if (multisampleFramebuffer != null) { - multisampleFramebuffer.dispose(); - multisampleFramebuffer = null; - } - } - } finally { - super.finalize(); - } - } protected void setFlushMode(int mode) { flushMode = mode; } + protected void updatePixelSize() { + float f = pgl.getPixelScale(); + pixelWidth = (int)(width * f); + pixelHeight = (int)(height * f); + } + + ////////////////////////////////////////////////////////////// - // IMAGE METADATA FOR THIS RENDERER + // PLATFORM-SPECIFIC CODE (Java, Android, etc.). Needs to be manually edited. + + + // Factory method + protected PGL createPGL(PGraphicsOpenGL pg) { // ignore +// return new PJOGL(pg); + return new PGLES(pg); + } + /* @Override - public void setCache(PImage image, Object storage) { - getPrimaryPG().cacheMap.put(image, storage); + // Java only + public PSurface createSurface() { // ignore + return surface = new PSurfaceJOGL(this); + } +*/ + + @Override + // Android only + public PSurface createSurface(AppComponent component, SurfaceHolder holder, boolean reset) { // ignore + if (reset) pgl.resetFBOLayer(); + return new PSurfaceGLES(this, component, holder); } @Override - public Object getCache(PImage image) { - return getPrimaryPG().cacheMap.get(image); + // Android only + public void setFrameRate(float frameRate) { + pgl.setFrameRate(frameRate); } @Override - public void removeCache(PImage image) { - getPrimaryPG().cacheMap.remove(image); + protected boolean isLooping() { // ignore + return super.isLooping(); } - ////////////////////////////////////////////////////////////// + public boolean saveImpl(String filename) { + return super.save(filename); // ASYNC save frame using PBOs not yet available on Android + /* + if (getHint(DISABLE_ASYNC_SAVEFRAME)) { + // Act as an opaque surface for the purposes of saving. + if (primaryGraphics) { + int prevFormat = format; + format = RGB; + boolean result = super.save(filename); + format = prevFormat; + return result; + } - protected void setFontTexture(PFont font, FontTexture fontTexture) { - getPrimaryPG().fontMap.put(font, fontTexture); - } + return super.save(filename); + } + if (asyncImageSaver == null) { + asyncImageSaver = new AsyncImageSaver(); + } - protected FontTexture getFontTexture(PFont font) { - return getPrimaryPG().fontMap.get(font); - } + if (!asyncPixelReaderInitialized) { + // First call! Get this guy initialized + if (pgl.hasPBOs() && pgl.hasSynchronization()) { + asyncPixelReader = new AsyncPixelReader(); + } + asyncPixelReaderInitialized = true; + } + + if (asyncPixelReader != null && !loaded) { + boolean needEndDraw = false; + if (!drawing) { + beginDraw(); + needEndDraw = true; + } + flush(); + updatePixelSize(); + // get the whole async package + asyncPixelReader.readAndSaveAsync(filename); - protected void removeFontTexture(PFont font) { - getPrimaryPG().fontMap.remove(font); + if (needEndDraw) endDraw(); + } else { + // async transfer is not supported or + // pixels are already in memory, just do async save + if (!loaded) loadPixels(); + int format = primaryGraphics ? RGB : ARGB; + PImage target = asyncImageSaver.getAvailableTarget(pixelWidth, pixelHeight, + format); + if (target == null) return false; + int count = PApplet.min(pixels.length, target.pixels.length); + System.arraycopy(pixels, 0, target.pixels, 0, count); + asyncImageSaver.saveTargetAsync(this, target, filename); + } + + return true; + */ } ////////////////////////////////////////////////////////////// - // RESOURCE HANDLING + // EYE/OBJECT MATRICES - protected static class GLResource { - int id; - int context; + @Override + public PMatrix3D getEyeMatrix() { + return getEyeMatrix(null); + } - GLResource(int id, int context) { - this.id = id; - this.context = context; - } - @Override - public boolean equals(Object obj) { - GLResource other = (GLResource)obj; - return other.id == id && other.context == context; + @Override + public PMatrix3D getEyeMatrix(PMatrix3D target) { + if (target == null) { + target = new PMatrix3D(); } + float sign = cameraUp ? +1 : -1; + target.set(rightX, sign * upX, forwardX, cameraX, + rightY, sign * upY, forwardY, cameraY, + rightZ, sign * upZ, forwardZ, cameraZ, + 0, 0, 0, 1); + return target; + } - @Override - public int hashCode() { - int result = 17; - result = 31 * result + id; - result = 31 * result + context; - return result; + + @Override + public PMatrix3D getObjectMatrix() { + PMatrix3D mat = new PMatrix3D(); + mat.set(modelviewInv); + mat.apply(camera); + return mat; + } + + + @Override + public PMatrix3D getObjectMatrix(PMatrix3D target) { + if (target == null) { + target = new PMatrix3D(); } + target.set(modelviewInv); + target.apply(camera); + return target; } - // Texture Objects ----------------------------------------------------------- + @Override + public void eye() { + eyeMatrix = getEyeMatrix(eyeMatrix); - protected static int createTextureObject(int context, PGL pgl) { - deleteFinalizedTextureObjects(pgl); + // Erasing any previous transformation in modelview + modelview.set(camera); + modelview.apply(eyeMatrix); - pgl.genTextures(1, intBuffer); - int id = intBuffer.get(0); + // The 3x3 block of eyeMatrix is orthogonal, so taking the transpose inverts it... + eyeMatrix.transpose(); + // ...and then invert the translation separately: + eyeMatrix.m03 = -cameraX; + eyeMatrix.m13 = -cameraY; + eyeMatrix.m23 = -cameraZ; + eyeMatrix.m30 = 0; + eyeMatrix.m31 = 0; + eyeMatrix.m32 = 0; - GLResource res = new GLResource(id, context); - if (!glTextureObjects.containsKey(res)) { - glTextureObjects.put(res, false); - } + // Applying the inverse of the previous transformations in the opposite order + // to compute the modelview inverse + modelviewInv.set(eyeMatrix); + modelviewInv.preApply(cameraInv); - return id; + updateProjmodelview(); } - protected static void deleteTextureObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glTextureObjects.containsKey(res)) { - intBuffer.put(0, id); - if (pgl.threadIsCurrent()) pgl.deleteTextures(1, intBuffer); - glTextureObjects.remove(res); + + ////////////////////////////////////////////////////////////// + + // RAY CASTING + + @Override + public PVector[] getRayFromScreen(float screenX, float screenY, PVector[] ray) { + if (ray == null || ray.length < 2) { + ray = new PVector[2]; + ray[0] = new PVector(); + ray[1] = new PVector(); } + getRayFromScreen(screenX, screenY, ray[0], ray[1]); + return ray; } - protected static void deleteAllTextureObjects(PGL pgl) { - for (GLResource res : glTextureObjects.keySet()) { - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteTextures(1, intBuffer); - } - glTextureObjects.clear(); + + @Override + public void getRayFromScreen(float screenX, float screenY, PVector origin, PVector direction) { + eyeMatrix = getEyeMatrix(eyeMatrix); + + // Transforming screen coordinates to world coordinates + screen.x = screenX; + screen.y = screenY; + screen.z = 0; + eyeMatrix.mult(screen, origin); + + // The direction of the ray is simply extracted from the third column of the eye matrix (the + // forward vector). + direction.set(eyeMatrix.m02, eyeMatrix.m12, eyeMatrix.m22); } - // This is synchronized because it is called from the GC thread. - synchronized protected static void finalizeTextureObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glTextureObjects.containsKey(res)) { - glTextureObjects.put(res, true); - } + + @Override + public boolean intersectsSphere(float r, float screenX, float screenY) { + ray = getRayFromScreen(screenX, screenY, ray); + return intersectsSphere(r, ray[0], ray[1]); } - protected static void deleteFinalizedTextureObjects(PGL pgl) { - Set finalized = new HashSet(); - for (GLResource res : glTextureObjects.keySet()) { - if (glTextureObjects.get(res)) { - finalized.add(res); - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteTextures(1, intBuffer); - } - } + @Override + public boolean intersectsSphere(float r, PVector origin, PVector direction) { + objMatrix = getObjectMatrix(objMatrix); + objMatrix.mult(origin, origInObjCoord); + PVector.add(origin, direction, hit); + objMatrix.mult(hit, hitInObjCoord); + PVector.sub(hitInObjCoord, origInObjCoord, dirInObjCoord); - for (GLResource res : finalized) { - glTextureObjects.remove(res); - } + return rayIntersectsSphere(origInObjCoord, dirInObjCoord, r); } - protected static void removeTextureObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glTextureObjects.containsKey(res)) { - glTextureObjects.remove(res); - } - } - // Vertex Buffer Objects ----------------------------------------------------- + // Ray-sphere intersecton algorithm as described in: + // http://paulbourke.net/geometry/circlesphere/ + private boolean rayIntersectsSphere(PVector orig, PVector dir, float r) { + float d = orig.mag(); - protected static int createVertexBufferObject(int context, PGL pgl) { - deleteFinalizedVertexBufferObjects(pgl); + // The eye is inside the sphere + if (d <= r) return true; - pgl.genBuffers(1, intBuffer); - int id = intBuffer.get(0); + float p = PVector.dot(orig, dir); - GLResource res = new GLResource(id, context); - if (!glVertexBuffers.containsKey(res)) { - glVertexBuffers.put(res, false); - } + // Check if sphere is in front of ray + if (p > 0) return false; - return id; + // Check intersection of ray with sphere + float b = 2 * p; + float c = d * d - r * r; + float det = b * b - 4 * c; + return det >= 0; } - protected static void deleteVertexBufferObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glVertexBuffers.containsKey(res)) { - intBuffer.put(0, id); - if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); - glVertexBuffers.remove(res); - } + + @Override + public boolean intersectsBox(float size, float screenX, float screenY) { + ray = getRayFromScreen(screenX, screenY, ray); + return intersectsBox(size, size, size, ray[0], ray[1]); } - protected static void deleteAllVertexBufferObjects(PGL pgl) { - for (GLResource res : glVertexBuffers.keySet()) { - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); - } - glVertexBuffers.clear(); + + @Override + public boolean intersectsBox(float w, float h, float d, float screenX, float screenY) { + ray = getRayFromScreen(screenX, screenY, ray); + return intersectsBox(w, h, d, ray[0], ray[1]); } - // This is synchronized because it is called from the GC thread. - synchronized static protected void finalizeVertexBufferObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glVertexBuffers.containsKey(res)) { - glVertexBuffers.put(res, true); - } + + @Override + public boolean intersectsBox(float size, PVector origin, PVector direction) { + return intersectsBox(size, size, size, origin, direction); } - protected static void deleteFinalizedVertexBufferObjects(PGL pgl) { - Set finalized = new HashSet(); - for (GLResource res : glVertexBuffers.keySet()) { - if (glVertexBuffers.get(res)) { - finalized.add(res); - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); - } - } + @Override + public boolean intersectsBox(float w, float h, float d, PVector origin, PVector direction) { + objMatrix = getObjectMatrix(objMatrix); + objMatrix.mult(origin, origInObjCoord); + PVector.add(origin, direction, hit); + objMatrix.mult(hit, hitInObjCoord); + PVector.sub(hitInObjCoord, origInObjCoord, dirInObjCoord); - for (GLResource res : finalized) { - glVertexBuffers.remove(res); - } + return lineIntersectsAABB(origInObjCoord, dirInObjCoord, w, h, d); } - protected static void removeVertexBufferObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glVertexBuffers.containsKey(res)) { - glVertexBuffers.remove(res); - } - } - // FrameBuffer Objects ------------------------------------------------------- + // Line intersection with an axis-aligned bounding box (AABB), calculated using the algorithm + // from Amy William et al: http:// dl.acm.org/citation.cfm?id=1198748 + private boolean lineIntersectsAABB(PVector orig, PVector dir, float w, float h, float d) { + float minx = -w/2; + float miny = -h/2; + float minz = -d/2; - protected static int createFrameBufferObject(int context, PGL pgl) { - deleteFinalizedFrameBufferObjects(pgl); + float maxx = +w/2; + float maxy = +h/2; + float maxz = +d/2; - pgl.genFramebuffers(1, intBuffer); - int id = intBuffer.get(0); + float idx = 1/dir.x; + float idy = 1/dir.y; + float idz = 1/dir.z; - GLResource res = new GLResource(id, context); - if (!glFrameBuffers.containsKey(res)) { - glFrameBuffers.put(res, false); - } + boolean sdx = idx < 0; + boolean sdy = idy < 0; + boolean sdz = idz < 0; - return id; - } + float bbx = sdx ? maxx : minx; + float txmin = (bbx - orig.x) * idx; + bbx = sdx ? minx : maxx; + float txmax = (bbx - orig.x) * idx; + float bby = sdy ? maxy : miny; + float tymin = (bby - orig.y) * idy; + bby = sdy ? miny : maxy; + float tymax = (bby - orig.y) * idy; - protected static void deleteFrameBufferObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glFrameBuffers.containsKey(res)) { - intBuffer.put(0, id); - if (pgl.threadIsCurrent()) pgl.deleteFramebuffers(1, intBuffer); - glFrameBuffers.remove(res); + if ((txmin > tymax) || (tymin > txmax)) { + return false; } - } - - protected static void deleteAllFrameBufferObjects(PGL pgl) { - for (GLResource res : glFrameBuffers.keySet()) { - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteFramebuffers(1, intBuffer); + if (tymin > txmin) { + txmin = tymin; } - glFrameBuffers.clear(); - } - - // This is synchronized because it is called from the GC thread. - synchronized static protected void finalizeFrameBufferObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glFrameBuffers.containsKey(res)) { - glFrameBuffers.put(res, true); + if (tymax < txmax) { + txmax = tymax; } - } - protected static void deleteFinalizedFrameBufferObjects(PGL pgl) { - Set finalized = new HashSet(); + float bbz = sdz ? maxz : minz; + float tzmin = (bbz - orig.z) * idz; + bbz = sdz ? minz : maxz; + float tzmax = (bbz - orig.z) * idz; - for (GLResource res : glFrameBuffers.keySet()) { - if (glFrameBuffers.get(res)) { - finalized.add(res); - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) { - pgl.deleteFramebuffers(1, intBuffer); - } - } + if ((txmin > tzmax) || (tzmin > txmax)) { + return false; + } + if (tzmin > txmin) { + txmin = tzmin; + } + if (tzmax < txmax) { + txmax = tzmax; } - for (GLResource res : finalized) { - glFrameBuffers.remove(res); + if ((txmin < defCameraFar) && (txmax > 0)) { + // The intersection coordinates: + // x = orig.x + txmin * dir.x; + // y = orig.y + txmin * dir.y; + // z = orig.z + txmin * dir.z; + return true; } + + return false; } - protected static void removeFrameBufferObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glFrameBuffers.containsKey(res)) { - glFrameBuffers.remove(res); - } + + @Override + public PVector intersectsPlane(float screenX, float screenY) { + ray = getRayFromScreen(screenX, screenY, ray); + return intersectsPlane(ray[0], ray[1]); } - // RenderBuffer Objects ------------------------------------------------------ - protected static int createRenderBufferObject(int context, PGL pgl) { - deleteFinalizedRenderBufferObjects(pgl); + @Override + public PVector intersectsPlane(PVector origin, PVector direction) { + modelview.mult(origin, origInWorldCoord); + modelview.mult(direction, dirInWorldCoord); + dirInWorldCoord.normalize(); - pgl.genRenderbuffers(1, intBuffer); - int id = intBuffer.get(0); + // Plane representation + PVector point = new PVector(0, 0, 0); + PVector normal = new PVector(0, 0, 1); - GLResource res = new GLResource(id, context); - if (!glRenderBuffers.containsKey(res)) { - glRenderBuffers.put(res, false); - } + // Ray-plane intersection algorithm + float d = PApplet.abs(PVector.dot(normal, dirInWorldCoord)); + if (d == 0) return null; - return id; - } + PVector w = PVector.sub(point, origInWorldCoord); + float k = PApplet.abs(PVector.dot(normal, w)/d); + PVector p = PVector.add(origInWorldCoord, dirInWorldCoord).setMag(k); - protected static void deleteRenderBufferObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glRenderBuffers.containsKey(res)) { - intBuffer.put(0, id); - if (pgl.threadIsCurrent()) pgl.deleteRenderbuffers(1, intBuffer); - glRenderBuffers.remove(res); - } + return p; } - protected static void deleteAllRenderBufferObjects(PGL pgl) { - for (GLResource res : glRenderBuffers.keySet()) { - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteRenderbuffers(1, intBuffer); - } - glRenderBuffers.clear(); - } - // This is synchronized because it is called from the GC thread. - synchronized static protected void finalizeRenderBufferObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glRenderBuffers.containsKey(res)) { - glRenderBuffers.put(res, true); - } - } + ////////////////////////////////////////////////////////////// - protected static void deleteFinalizedRenderBufferObjects(PGL pgl) { - Set finalized = new HashSet(); + // IMAGE METADATA FOR THIS RENDERER - for (GLResource res : glRenderBuffers.keySet()) { - if (glRenderBuffers.get(res)) { - finalized.add(res); - intBuffer.put(0, res.id); - if (pgl.threadIsCurrent()) pgl.deleteRenderbuffers(1, intBuffer); - } - } - for (GLResource res : finalized) { - glRenderBuffers.remove(res); + @Override + public void setCache(PImage image, Object storage) { + if (image instanceof PGraphicsOpenGL) { + // Prevent strong reference to the key from the value by wrapping + // the Texture into WeakReference (proposed solution by WeakHashMap docs) + getPrimaryPG().cacheMap.put(image, new WeakReference<>(storage)); + return; } + getPrimaryPG().cacheMap.put(image, storage); } - protected static void removeRenderBufferObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glRenderBuffers.containsKey(res)) { - glRenderBuffers.remove(res); + + @Override + @SuppressWarnings("rawtypes") + public Object getCache(PImage image) { + Object storage = getPrimaryPG().cacheMap.get(image); + if (storage != null && storage.getClass() == WeakReference.class) { + // Unwrap the value, use getClass() for fast check + return ((WeakReference) storage).get(); } + return storage; } - // GLSL Program Objects ------------------------------------------------------ - protected static int createGLSLProgramObject(int context, PGL pgl) { - deleteFinalizedGLSLProgramObjects(pgl); + @Override + public void removeCache(PImage image) { + getPrimaryPG().cacheMap.remove(image); + } + - int id = pgl.createProgram(); + ////////////////////////////////////////////////////////////// - GLResource res = new GLResource(id, context); - if (!glslPrograms.containsKey(res)) { - glslPrograms.put(res, false); - } - return id; + protected void setFontTexture(PFont font, FontTexture fontTexture) { + getPrimaryPG().fontMap.put(font, fontTexture); } - protected static void deleteGLSLProgramObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glslPrograms.containsKey(res)) { - if (pgl.threadIsCurrent()) pgl.deleteProgram(res.id); - glslPrograms.remove(res); - } - } - protected static void deleteAllGLSLProgramObjects(PGL pgl) { - for (GLResource res : glslPrograms.keySet()) { - if (pgl.threadIsCurrent()) pgl.deleteProgram(res.id); - } - glslPrograms.clear(); + protected FontTexture getFontTexture(PFont font) { + return getPrimaryPG().fontMap.get(font); } - // This is synchronized because it is called from the GC thread. - synchronized static protected void finalizeGLSLProgramObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glslPrograms.containsKey(res)) { - glslPrograms.put(res, true); - } + + protected void removeFontTexture(PFont font) { + getPrimaryPG().fontMap.remove(font); } - protected static void deleteFinalizedGLSLProgramObjects(PGL pgl) { - Set finalized = new HashSet(); - for (GLResource res : glslPrograms.keySet()) { - if (glslPrograms.get(res)) { - finalized.add(res); - if (pgl.threadIsCurrent()) pgl.deleteProgram(res.id); + ////////////////////////////////////////////////////////////// + + + protected static class GLResourceTexture extends Disposable { + int glName; + + private PGL pgl; + private int context; + + public GLResourceTexture(Texture tex) { + super(tex); + + + pgl = tex.pg.getPrimaryPGL(); + pgl.genTextures(1, intBuffer); + tex.glName = intBuffer.get(0); + + this.glName = tex.glName; + this.context = tex.context; + } + + @Override + public void disposeNative() { + if (pgl != null) { + if (glName != 0) { + intBuffer.put(0, glName); + pgl.deleteTextures(1, intBuffer); + glName = 0; + } + pgl = null; } } - for (GLResource res : finalized) { - glslPrograms.remove(res); + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GLResourceTexture)) { + return false; + } + GLResourceTexture other = (GLResourceTexture)obj; + return other.glName == glName && + other.context == context; } - } - protected static void removeGLSLProgramObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glslPrograms.containsKey(res)) { - glslPrograms.remove(res); + @Override + public int hashCode() { + int result = 17; + result = 31 * result + glName; + result = 31 * result + context; + return result; } } - // GLSL Vertex Shader Objects ------------------------------------------------ - protected static int createGLSLVertShaderObject(int context, PGL pgl) { - deleteFinalizedGLSLVertShaderObjects(pgl); + protected static class GLResourceVertexBuffer extends Disposable { + int glId; - int id = pgl.createShader(PGL.VERTEX_SHADER); + private PGL pgl; + private int context; - GLResource res = new GLResource(id, context); - if (!glslVertexShaders.containsKey(res)) { - glslVertexShaders.put(res, false); - } + public GLResourceVertexBuffer(VertexBuffer vbo) { + super(vbo); - return id; - } + pgl = vbo.pgl.graphics.getPrimaryPGL(); + pgl.genBuffers(1, intBuffer); + vbo.glId = intBuffer.get(0); - protected static void deleteGLSLVertShaderObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glslVertexShaders.containsKey(res)) { - if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); - glslVertexShaders.remove(res); + this.glId = vbo.glId; + this.context = vbo.context; } - } - protected static void deleteAllGLSLVertShaderObjects(PGL pgl) { - for (GLResource res : glslVertexShaders.keySet()) { - if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + @Override + public void disposeNative() { + if (pgl != null) { + if (glId != 0) { + intBuffer.put(0, glId); + pgl.deleteBuffers(1, intBuffer); + glId = 0; + } + pgl = null; + } } - glslVertexShaders.clear(); - } - // This is synchronized because it is called from the GC thread. - synchronized static protected void finalizeGLSLVertShaderObject(int id, - int context) { - GLResource res = new GLResource(id, context); - if (glslVertexShaders.containsKey(res)) { - glslVertexShaders.put(res, true); + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GLResourceVertexBuffer)) { + return false; + } + GLResourceVertexBuffer other = (GLResourceVertexBuffer)obj; + return other.glId == glId && + other.context == context; + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + glId; + result = 31 * result + context; + return result; } } - protected static void deleteFinalizedGLSLVertShaderObjects(PGL pgl) { - Set finalized = new HashSet(); - for (GLResource res : glslVertexShaders.keySet()) { - if (glslVertexShaders.get(res)) { - finalized.add(res); - if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + protected static class GLResourceShader extends Disposable { + int glProgram; + int glVertex; + int glFragment; + + private PGL pgl; + private int context; + + public GLResourceShader(PShader sh) { + super(sh); + + this.pgl = sh.pgl.graphics.getPrimaryPGL(); + sh.glProgram = pgl.createProgram(); + sh.glVertex = pgl.createShader(PGL.VERTEX_SHADER); + sh.glFragment = pgl.createShader(PGL.FRAGMENT_SHADER); + + this.glProgram = sh.glProgram; + this.glVertex = sh.glVertex; + this.glFragment = sh.glFragment; + + this.context = sh.context; + } + + @Override + public void disposeNative() { + if (pgl != null) { + if (glFragment != 0) { + pgl.deleteShader(glFragment); + glFragment = 0; + } + if (glVertex != 0) { + pgl.deleteShader(glVertex); + glVertex = 0; + } + if (glProgram != 0) { + pgl.deleteProgram(glProgram); + glProgram = 0; + } + pgl = null; } } - for (GLResource res : finalized) { - glslVertexShaders.remove(res); + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GLResourceShader)) { + return false; + } + GLResourceShader other = (GLResourceShader)obj; + return other.glProgram == glProgram && + other.glVertex == glVertex && + other.glFragment == glFragment && + other.context == context; } - } - protected static void removeGLSLVertShaderObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glslVertexShaders.containsKey(res)) { - glslVertexShaders.remove(res); + @Override + public int hashCode() { + int result = 17; + result = 31 * result + glProgram; + result = 31 * result + glVertex; + result = 31 * result + glFragment; + result = 31 * result + context; + return result; } } - // GLSL Fragment Shader Objects ---------------------------------------------- - protected static int createGLSLFragShaderObject(int context, PGL pgl) { - deleteFinalizedGLSLFragShaderObjects(pgl); + protected static class GLResourceFrameBuffer extends Disposable { + int glFbo; + int glDepth; + int glStencil; + int glDepthStencil; + int glMultisample; - int id = pgl.createShader(PGL.FRAGMENT_SHADER); + private PGL pgl; + private int context; - GLResource res = new GLResource(id, context); - if (!glslFragmentShaders.containsKey(res)) { - glslFragmentShaders.put(res, false); - } + public GLResourceFrameBuffer(FrameBuffer fb) { + super(fb); - return id; - } + pgl = fb.pg.getPrimaryPGL(); + if (!fb.screenFb) { + pgl.genFramebuffers(1, intBuffer); + fb.glFbo = intBuffer.get(0); - protected static void deleteGLSLFragShaderObject(int id, int context, PGL pgl) { - GLResource res = new GLResource(id, context); - if (glslFragmentShaders.containsKey(res)) { - if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); - glslFragmentShaders.remove(res); - } - } + if (fb.multisample) { + pgl.genRenderbuffers(1, intBuffer); + fb.glMultisample = intBuffer.get(0); + } - protected static void deleteAllGLSLFragShaderObjects(PGL pgl) { - for (GLResource res : glslFragmentShaders.keySet()) { - if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); - } - glslFragmentShaders.clear(); - } + if (fb.packedDepthStencil) { + pgl.genRenderbuffers(1, intBuffer); + fb.glDepthStencil = intBuffer.get(0); + } else { + if (0 < fb.depthBits) { + pgl.genRenderbuffers(1, intBuffer); + fb.glDepth = intBuffer.get(0); + } + if (0 < fb.stencilBits) { + pgl.genRenderbuffers(1, intBuffer); + fb.glStencil = intBuffer.get(0); + } + } - // This is synchronized because it is called from the GC thread. - synchronized static protected void finalizeGLSLFragShaderObject(int id, - int context) { - GLResource res = new GLResource(id, context); - if (glslFragmentShaders.containsKey(res)) { - glslFragmentShaders.put(res, true); - } - } + this.glFbo = fb.glFbo; + this.glDepth = fb.glDepth; + this.glStencil = fb.glStencil; + this.glDepthStencil = fb.glDepthStencil; + this.glMultisample = fb.glMultisample; + } - protected static void deleteFinalizedGLSLFragShaderObjects(PGL pgl) { - Set finalized = new HashSet(); + this.context = fb.context; + } - for (GLResource res : glslFragmentShaders.keySet()) { - if (glslFragmentShaders.get(res)) { - finalized.add(res); - if (pgl.threadIsCurrent()) pgl.deleteShader(res.id); + @Override + public void disposeNative() { + if (pgl != null) { + if (glFbo != 0) { + intBuffer.put(0, glFbo); + pgl.deleteFramebuffers(1, intBuffer); + glFbo = 0; + } + if (glDepth != 0) { + intBuffer.put(0, glDepth); + pgl.deleteRenderbuffers(1, intBuffer); + glDepth = 0; + } + if (glStencil != 0) { + intBuffer.put(0, glStencil); + pgl.deleteRenderbuffers(1, intBuffer); + glStencil = 0; + } + if (glDepthStencil != 0) { + intBuffer.put(0, glDepthStencil); + pgl.deleteRenderbuffers(1, intBuffer); + glDepthStencil = 0; + } + if (glMultisample != 0) { + intBuffer.put(0, glMultisample); + pgl.deleteRenderbuffers(1, intBuffer); + glMultisample = 0; + } + pgl = null; } } - for (GLResource res : finalized) { - glslFragmentShaders.remove(res); + @Override + public boolean equals(Object obj) { + if (!(obj instanceof GLResourceFrameBuffer)) { + return false; + } + GLResourceFrameBuffer other = (GLResourceFrameBuffer)obj; + return other.glFbo == glFbo && + other.glDepth == glDepth && + other.glStencil == glStencil && + other.glDepthStencil == glDepthStencil && + other.glMultisample == glMultisample && + other.context == context; } - } - protected static void removeGLSLFragShaderObject(int id, int context) { - GLResource res = new GLResource(id, context); - if (glslFragmentShaders.containsKey(res)) { - glslFragmentShaders.remove(res); + @Override + public int hashCode() { + int result = 17; + result = 31 * result + glFbo; + result = 31 * result + glDepth; + result = 31 * result + glStencil; + result = 31 * result + glDepthStencil; + result = 31 * result + glMultisample; + result = 31 * result + context; + return result; } } - // All OpenGL resources ------------------------------------------------------ - - protected static void deleteFinalizedGLResources(PGL pgl) { - deleteFinalizedTextureObjects(pgl); - deleteFinalizedVertexBufferObjects(pgl); - deleteFinalizedFrameBufferObjects(pgl); - deleteFinalizedRenderBufferObjects(pgl); - deleteFinalizedGLSLProgramObjects(pgl); - deleteFinalizedGLSLVertShaderObjects(pgl); - deleteFinalizedGLSLFragShaderObjects(pgl); - } - ////////////////////////////////////////////////////////////// @@ -1262,52 +1508,30 @@ protected void createPolyBuffers() { if (!polyBuffersCreated || polyBuffersContextIsOutdated()) { polyBuffersContext = pgl.getCurrentContext(); - int sizef = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_FLOAT; - int sizei = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_INT; - int sizex = INIT_INDEX_BUFFER_SIZE * PGL.SIZEOF_INDEX; - - glPolyVertex = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex); - pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); - - glPolyColor = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); - - glPolyNormal = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal); - pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); - - glPolyTexcoord = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord); - pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, null, PGL.STATIC_DRAW); - - glPolyAmbient = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); - - glPolySpecular = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); - - glPolyEmissive = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); - - glPolyShininess = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess); - pgl.bufferData(PGL.ARRAY_BUFFER, sizef, null, PGL.STATIC_DRAW); - + bufPolyVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); + bufPolyColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + bufPolyNormal = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); + bufPolyTexcoord = new VertexBuffer(this, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); + bufPolyAmbient = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + bufPolySpecular = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + bufPolyEmissive = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + bufPolyShininess = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - - glPolyIndex = createVertexBufferObject(polyBuffersContext, pgl); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPolyIndex); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizex, null, PGL.STATIC_DRAW); - + bufPolyIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); polyBuffersCreated = true; } + + boolean created = false; + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (!attrib.bufferCreated() || polyBuffersContextIsOutdated()) { + attrib.createBuffer(pgl); + created = true; + } + } + if (created) pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -1320,52 +1544,61 @@ protected void updatePolyBuffers(boolean lit, boolean tex, int sizei = size * PGL.SIZEOF_INT; tessGeo.updatePolyVerticesBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, tessGeo.polyVerticesBuffer, PGL.STATIC_DRAW); tessGeo.updatePolyColorsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, tessGeo.polyColorsBuffer, PGL.STATIC_DRAW); if (lit) { tessGeo.updatePolyAmbientBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, tessGeo.polyAmbientBuffer, PGL.STATIC_DRAW); tessGeo.updatePolySpecularBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, tessGeo.polySpecularBuffer, PGL.STATIC_DRAW); tessGeo.updatePolyEmissiveBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, tessGeo.polyEmissiveBuffer, PGL.STATIC_DRAW); tessGeo.updatePolyShininessBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizef, tessGeo.polyShininessBuffer, PGL.STATIC_DRAW); } + if (lit || needNormals) { tessGeo.updatePolyNormalsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, tessGeo.polyNormalsBuffer, PGL.STATIC_DRAW); } if (tex || needTexCoords) { tessGeo.updatePolyTexCoordsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexcoord.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, tessGeo.polyTexCoordsBuffer, PGL.STATIC_DRAW); } + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + tessGeo.updateAttribBuffer(name); + pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); + pgl.bufferData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(size), + tessGeo.polyAttribBuffers.get(name), PGL.STATIC_DRAW); + } + tessGeo.updatePolyIndicesBuffer(); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPolyIndex); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPolyIndex.glId); pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, tessGeo.polyIndexCount * PGL.SIZEOF_INDEX, tessGeo.polyIndicesBuffer, PGL.STATIC_DRAW); @@ -1383,83 +1616,15 @@ protected boolean polyBuffersContextIsOutdated() { } - protected void finalizePolyBuffers() { - if (glPolyVertex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyVertex, polyBuffersContext); - glPolyVertex = 0; - } - - if (glPolyColor != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyColor, polyBuffersContext); - glPolyColor = 0; - } - - if (glPolyNormal != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyNormal, polyBuffersContext); - glPolyNormal = 0; - } - - if (glPolyTexcoord != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyTexcoord, polyBuffersContext); - glPolyTexcoord = 0; - } - - if (glPolyAmbient != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyAmbient, polyBuffersContext); - glPolyAmbient = 0; - } - - if (glPolySpecular != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolySpecular, polyBuffersContext); - glPolySpecular = 0; - } - - if (glPolyEmissive != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyEmissive, polyBuffersContext); - glPolyEmissive = 0; - } - - if (glPolyShininess != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyShininess, polyBuffersContext); - glPolyShininess = 0; - } - - if (glPolyIndex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyIndex, polyBuffersContext); - glPolyIndex = 0; - } - - polyBuffersCreated = false; - } - - protected void createLineBuffers() { if (!lineBuffersCreated || lineBufferContextIsOutdated()) { lineBuffersContext = pgl.getCurrentContext(); - int sizef = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_FLOAT; - int sizei = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_INT; - int sizex = INIT_INDEX_BUFFER_SIZE * PGL.SIZEOF_INDEX; - - glLineVertex = createVertexBufferObject(lineBuffersContext, pgl); - - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex); - pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); - - glLineColor = createVertexBufferObject(lineBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); - - glLineAttrib = createVertexBufferObject(lineBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib); - pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, null, PGL.STATIC_DRAW); - + bufLineVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); + bufLineColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + bufLineAttrib = new VertexBuffer(this, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - - glLineIndex = createVertexBufferObject(lineBuffersContext, pgl); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glLineIndex); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizex, null, PGL.STATIC_DRAW); - + bufLineIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); lineBuffersCreated = true; @@ -1474,23 +1639,25 @@ protected void updateLineBuffers() { int sizef = size * PGL.SIZEOF_FLOAT; int sizei = size * PGL.SIZEOF_INT; + + tessGeo.updateLineVerticesBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, tessGeo.lineVerticesBuffer, PGL.STATIC_DRAW); tessGeo.updateLineColorsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, tessGeo.lineColorsBuffer, PGL.STATIC_DRAW); tessGeo.updateLineDirectionsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, tessGeo.lineDirectionsBuffer, PGL.STATIC_DRAW); tessGeo.updateLineIndicesBuffer(); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glLineIndex); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufLineIndex.glId); pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, tessGeo.lineIndexCount * PGL.SIZEOF_INDEX, tessGeo.lineIndicesBuffer, PGL.STATIC_DRAW); @@ -1508,57 +1675,15 @@ protected boolean lineBufferContextIsOutdated() { } - protected void finalizeLineBuffers() { - if (glLineVertex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineVertex, lineBuffersContext); - glLineVertex = 0; - } - - if (glLineColor != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineColor, lineBuffersContext); - glLineColor = 0; - } - - if (glLineAttrib != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineAttrib, lineBuffersContext); - glLineAttrib = 0; - } - - if (glLineIndex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineIndex, lineBuffersContext); - glLineIndex = 0; - } - - lineBuffersCreated = false; - } - - protected void createPointBuffers() { if (!pointBuffersCreated || pointBuffersContextIsOutdated()) { pointBuffersContext = pgl.getCurrentContext(); - int sizef = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_FLOAT; - int sizei = INIT_VERTEX_BUFFER_SIZE * PGL.SIZEOF_INT; - int sizex = INIT_INDEX_BUFFER_SIZE * PGL.SIZEOF_INDEX; - - glPointVertex = createVertexBufferObject(pointBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex); - pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, null, PGL.STATIC_DRAW); - - glPointColor = createVertexBufferObject(pointBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor); - pgl.bufferData(PGL.ARRAY_BUFFER, sizei, null, PGL.STATIC_DRAW); - - glPointAttrib = createVertexBufferObject(pointBuffersContext, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib); - pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, null, PGL.STATIC_DRAW); - + bufPointVertex = new VertexBuffer(this, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); + bufPointColor = new VertexBuffer(this, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + bufPointAttrib = new VertexBuffer(this, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); - - glPointIndex = createVertexBufferObject(pointBuffersContext, pgl); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPointIndex); - pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, sizex, null, PGL.STATIC_DRAW); - + bufPointIndex = new VertexBuffer(this, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); pointBuffersCreated = true; @@ -1574,22 +1699,22 @@ protected void updatePointBuffers() { int sizei = size * PGL.SIZEOF_INT; tessGeo.updatePointVerticesBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, tessGeo.pointVerticesBuffer, PGL.STATIC_DRAW); tessGeo.updatePointColorsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, tessGeo.pointColorsBuffer, PGL.STATIC_DRAW); tessGeo.updatePointOffsetsBuffer(); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, tessGeo.pointOffsetsBuffer, PGL.STATIC_DRAW); tessGeo.updatePointIndicesBuffer(); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPointIndex); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPointIndex.glId); pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, tessGeo.pointIndexCount * PGL.SIZEOF_INDEX, tessGeo.pointIndicesBuffer, PGL.STATIC_DRAW); @@ -1607,69 +1732,23 @@ protected boolean pointBuffersContextIsOutdated() { } - protected void finalizePointBuffers() { - if (glPointVertex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointVertex, pointBuffersContext); - glPointVertex = 0; - } - - if (glPointColor != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointColor, pointBuffersContext); - glPointColor = 0; - } - - if (glPointAttrib != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointAttrib, pointBuffersContext); - glPointAttrib = 0; - } - - if (glPointIndex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointIndex, pointBuffersContext); - glPointIndex = 0; - } - - pointBuffersCreated = false; - } - - - @Override - public void requestFocus() { // ignore - pgl.requestFocus(); - } - - - /** - * OpenGL cannot draw until a proper native peer is available, so this - * returns the value of PApplet.isDisplayable() (inherited from Component). - */ - @Override - public boolean canDraw() { - return pgl.canDraw(); - } - - - @Override - public void requestDraw() { - if (primarySurface) { - if (initialized) { - if (sized) pgl.reinitSurface(); - if (parent.canDraw()) pgl.requestDraw(); - } else { - initPrimary(); - } - } - } - - @Override public void beginDraw() { - if (primarySurface) { + if (primaryGraphics) { + initPrimary(); setCurrentPG(this); } else { pgl.getGL(getPrimaryPGL()); getPrimaryPG().setCurrentPG(this); } +// if (!pgl.threadIsCurrent()) { +// PGraphics.showWarning(GL_THREAD_NOT_CURRENT); +// return; +// } + + // This has to go after the surface initialization, otherwise offscreen + // surfaces will have a null gl object. report("top beginDraw()"); if (!checkGLThread()) { @@ -1680,7 +1759,7 @@ public void beginDraw() { return; } - if (!primarySurface && getPrimaryPG().texCache.containsTexture(this)) { + if (!primaryGraphics && getPrimaryPG().texCache.containsTexture(this)) { // This offscreen surface is being used as a texture earlier in draw, // so we should update the rendering up to this point since it will be // modified. @@ -1692,12 +1771,12 @@ public void beginDraw() { } setViewport(); - if (primarySurface) { + if (primaryGraphics) { beginOnscreenDraw(); } else { beginOffscreenDraw(); } - setDrawDefaults(); // TODO: look at using checkSettings() instead... + checkSettings(); drawing = true; @@ -1716,27 +1795,16 @@ public void endDraw() { // Flushing any remaining geometry. flush(); - if (PGL.SAVE_SURFACE_TO_PIXELS_HACK && - (!getPrimaryPG().initialized || parent.frameCount == 0)) { - // Smooth was disabled/enabled at some point during drawing. We save - // the current contents of the back buffer (because the buffers haven't - // been swapped yet) to the pixels array. The frameCount == 0 condition - // is to handle the situation when no smooth is called in setup in the - // PDE, but the OpenGL appears to be recreated due to the size() nastiness. - saveSurfaceToPixels(); - restoreSurface = true; - } - - if (primarySurface) { + if (primaryGraphics) { endOnscreenDraw(); } else { endOffscreenDraw(); } - if (primarySurface) { + if (primaryGraphics) { setCurrentPG(null); } else { - getPrimaryPG().setCurrentPG(getPrimaryPG()); + getPrimaryPG().setCurrentPG(); } drawing = false; @@ -1744,14 +1812,8 @@ public void endDraw() { } - // Factory method - protected PGL createPGL(PGraphicsOpenGL pg) { - return new PGLES(pg); - } - - protected PGraphicsOpenGL getPrimaryPG() { - if (primarySurface) { + if (primaryGraphics) { return this; } else { return (PGraphicsOpenGL)parent.g; @@ -1762,12 +1824,16 @@ protected void setCurrentPG(PGraphicsOpenGL pg) { currentPG = pg; } + protected void setCurrentPG() { + currentPG = this; + } + protected PGraphicsOpenGL getCurrentPG() { return currentPG; } protected PGL getPrimaryPGL() { - if (primarySurface) { + if (primaryGraphics) { return pgl; } else { return ((PGraphicsOpenGL)parent.g).pgl; @@ -1811,7 +1877,7 @@ protected void restoreGL() { } pgl.depthFunc(PGL.LEQUAL); - if (quality < 2) { + if (smooth < 1) { pgl.disable(PGL.MULTISAMPLE); } else { pgl.enable(PGL.MULTISAMPLE); @@ -1827,7 +1893,7 @@ protected void restoreGL() { pgl.disable(PGL.SCISSOR_TEST); } - pgl.frontFace(PGL.CW); + pgl.frontFace(cameraUp ? PGL.CCW : PGL.CW); pgl.disable(PGL.CULL_FACE); pgl.activeTexture(PGL.TEXTURE0); @@ -1841,7 +1907,7 @@ protected void restoreGL() { FrameBuffer fb = getCurrentFB(); if (fb != null) { fb.bind(); - pgl.drawBuffer(fb.getDefaultDrawBuffer()); + if (drawBufferSupported) pgl.drawBuffer(fb.getDefaultDrawBuffer()); } } @@ -1868,40 +1934,56 @@ protected void endReadPixels() { protected void beginPixelsOp(int op) { FrameBuffer pixfb = null; - if (primarySurface) { - if (op == OP_READ) { - if (pgl.isFBOBacked() && pgl.isMultisampled()) { - // Making sure the back texture is up-to-date... - pgl.syncBackTexture(); - // ...because the read framebuffer uses it as the color buffer (the - // draw framebuffer is MSAA so it cannot be read from it). - pixfb = readFramebuffer; - } else { - pixfb = drawFramebuffer; + FrameBuffer currfb = getCurrentFB(); + if (primaryGraphics) { + FrameBuffer rfb = readFramebuffer; + FrameBuffer dfb = drawFramebuffer; + if ((currfb == rfb) || (currfb == dfb)) { + // Not user-provided FB, need to check if the correct FB is current. + if (op == OP_READ) { + if (pgl.isFBOBacked() && pgl.isMultisampled()) { + // Making sure the back texture is up-to-date... + pgl.syncBackTexture(); + // ...because the read framebuffer uses it as the color buffer (the + // draw framebuffer is MSAA so it cannot be read from it). + pixfb = rfb; + } else { + pixfb = dfb; + } + } else if (op == OP_WRITE) { + // We can write to the draw framebuffer irrespective of whether is + // FBO-baked or multisampled. + pixfb = dfb; } - } else if (op == OP_WRITE) { - // We can write to the draw framebuffer irrespective of whether is - // FBO-baked or multisampled. - pixfb = drawFramebuffer; } } else { - if (op == OP_READ) { - if (offscreenMultisample) { - // Making sure the offscreen FBO is up-to-date - multisampleFramebuffer.copyColor(offscreenFramebuffer); + FrameBuffer ofb = offscreenFramebuffer; + FrameBuffer mfb = multisampleFramebuffer; + if ((currfb == ofb) || (currfb == mfb)) { + // Not user-provided FB, need to check if the correct FB is current. + if (op == OP_READ) { + if (offscreenMultisample) { + // Making sure the offscreen FBO is up-to-date + int mask = PGL.COLOR_BUFFER_BIT; + if (hints[ENABLE_BUFFER_READING]) { + mask |= PGL.DEPTH_BUFFER_BIT | PGL.STENCIL_BUFFER_BIT; + } + if (ofb != null && mfb != null) { + mfb.copy(ofb, mask); + } + } + // We always read the screen pixels from the color FBO. + pixfb = ofb; + } else if (op == OP_WRITE) { + // We can write directly to the color FBO, or to the multisample FBO + // if multisampling is enabled. + pixfb = offscreenMultisample ? mfb : ofb; } - // We always read the screen pixels from the color FBO. - pixfb = offscreenFramebuffer; - } else if (op == OP_WRITE) { - // We can write directly to the color FBO, or to the multisample FBO - // if multisampling is enabled. - pixfb = offscreenMultisample ? multisampleFramebuffer : - offscreenFramebuffer; } } // Set the framebuffer where the pixel operation shall be carried out. - if (pixfb != getCurrentFB()) { + if (pixfb != null && pixfb != getCurrentFB()) { pushFramebuffer(); setFramebuffer(pixfb); pixOpChangedFB = true; @@ -1909,9 +1991,9 @@ protected void beginPixelsOp(int op) { // We read from/write to the draw buffer. if (op == OP_READ) { - pgl.readBuffer(getCurrentFB().getDefaultDrawBuffer()); + if (readBufferSupported) pgl.readBuffer(getCurrentFB().getDefaultDrawBuffer()); } else if (op == OP_WRITE) { - pgl.drawBuffer(getCurrentFB().getDefaultDrawBuffer()); + if (drawBufferSupported) pgl.drawBuffer(getCurrentFB().getDefaultDrawBuffer()); } pixelsOp = op; @@ -1926,8 +2008,8 @@ protected void endPixelsOp() { } // Restoring default read/draw buffer configuration. - pgl.readBuffer(getCurrentFB().getDefaultReadBuffer()); - pgl.drawBuffer(getCurrentFB().getDefaultDrawBuffer()); + if (readBufferSupported) pgl.readBuffer(getCurrentFB().getDefaultReadBuffer()); + if (drawBufferSupported) pgl.drawBuffer(getCurrentFB().getDefaultDrawBuffer()); pixelsOp = OP_NONE; } @@ -2050,8 +2132,6 @@ protected void defaultSettings() { manipulatingCamera = false; - clearColorBuffer = false; - // easiest for beginners textureMode(IMAGE); @@ -2115,6 +2195,23 @@ public void hint(int which) { // We flush the geometry using the previous line setting. flush(); } + } else if (which == ENABLE_DEPTH_SORT) { + if (is3D()) { + flush(); + if (sorter == null) sorter = new DepthSorter(this); + isDepthSortingEnabled = true; + } else { + PGraphics.showWarning("Depth sorting can only be enabled in 3D"); + } + } else if (which == DISABLE_DEPTH_SORT) { + if (is3D()) { + flush(); + isDepthSortingEnabled = false; + } + } else if (which == ENABLE_BUFFER_READING) { + restartPGL(); + } else if (which == DISABLE_BUFFER_READING) { + restartPGL(); } } @@ -2128,6 +2225,32 @@ protected boolean getHint(int which) { } + ////////////////////////////////////////////////////////////// + + // CREATE SHAPE + + + @Override + protected PShape createShapeFamily(int type) { + PShape shape = new PShapeOpenGL(this, type); + if (is3D()) { + shape.set3D(true); + } + return shape; + } + + + @Override + protected PShape createShapePrimitive(int kind, float... p) { + PShape shape = new PShapeOpenGL(this, kind, p); + if (is3D()) { + shape.set3D(true); + } + return shape; + } + + + ////////////////////////////////////////////////////////////// // VERTEX SHAPES @@ -2158,7 +2281,7 @@ public void endShape(int mode) { flush(); } else { // pixels array is not up-to-date anymore - arePixelsUpToDate = false; + loaded = false; } } @@ -2176,7 +2299,7 @@ protected void endShape(int[] indices) { flush(); } else { // pixels array is not up-to-date anymore - arePixelsUpToDate = false; + loaded = false; } } @@ -2239,6 +2362,81 @@ public void vertex(float x, float y, float z, float u, float v) { } + @Override + public void attribPosition(String name, float x, float y, float z) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.POSITION, + PGL.FLOAT, 3); + if (attrib != null) attrib.set(x, y, z); + } + + + @Override + public void attribNormal(String name, float nx, float ny, float nz) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.NORMAL, + PGL.FLOAT, 3); + if (attrib != null) attrib.set(nx, ny, nz); + } + + + @Override + public void attribColor(String name, int color) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.COLOR, PGL.INT, 1); + if (attrib != null) attrib.set(new int[] {color}); + } + + + @Override + public void attrib(String name, float... values) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, + PGL.FLOAT, values.length); + if (attrib != null) attrib.set(values); + } + + + @Override + public void attrib(String name, int... values) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, + PGL.INT, values.length); + if (attrib != null) attrib.set(values); + } + + + @Override + public void attrib(String name, boolean... values) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, + PGL.BOOL, values.length); + if (attrib != null) attrib.set(values); + } + + + protected VertexAttribute attribImpl(String name, int kind, int type, int size) { + if (4 < size) { + PGraphics.showWarning("Vertex attributes cannot have more than 4 values"); + return null; + } + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) { + attrib = new VertexAttribute(this, name, kind, type, size); + polyAttribs.put(name, attrib); + inGeo.initAttrib(attrib); + tessGeo.initAttrib(attrib); + } + if (attrib.kind != kind) { + PGraphics.showWarning("The attribute kind cannot be changed after creation"); + return null; + } + if (attrib.type != type) { + PGraphics.showWarning("The attribute type cannot be changed after creation"); + return null; + } + if (attrib.size != size) { + PGraphics.showWarning("New value for vertex attribute has wrong number of values"); + return null; + } + return attrib; + } + + protected void vertexImpl(float x, float y, float z, float u, float v) { boolean textured = textureImage != null; int fcolor = 0x00; @@ -2363,7 +2561,7 @@ protected void tessellate(int mode) { if (normalMode == NORMAL_MODE_AUTO) inGeo.calcQuadStripNormals(); tessellator.tessellateQuadStrip(); } else if (shape == POLYGON) { - tessellator.tessellatePolygon(false, mode == CLOSE, + tessellator.tessellatePolygon(true, mode == CLOSE, normalMode == NORMAL_MODE_AUTO); } } @@ -2422,7 +2620,7 @@ public void flush() { projmodelview.set(projection); } - if (hasPolys) { + if (hasPolys && !isDepthSortingEnabled) { flushPolys(); if (raw != null) { rawPolys(); @@ -2445,16 +2643,26 @@ public void flush() { } } + if (hasPolys && isDepthSortingEnabled) { + // We flush after lines so they are visible + // under transparent polygons + flushSortedPolys(); + if (raw != null) { + rawSortedPolys(); + } + } + if (flushMode == FLUSH_WHEN_FULL) { modelview = modelview0; modelviewInv = modelviewInv0; updateProjmodelview(); } + + loaded = false; } tessGeo.clear(); texCache.clear(); - arePixelsUpToDate = false; } @@ -2489,256 +2697,154 @@ protected void flushPolys() { cache.indexOffset[n] + cache.indexCount[n] - ioffset; int voffset = cache.vertexOffset[n]; - shader.setVertexAttribute(glPolyVertex, 4, PGL.FLOAT, 0, + shader.setVertexAttribute(bufPolyVertex.glId, 4, PGL.FLOAT, 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.setColorAttribute(glPolyColor, 4, PGL.UNSIGNED_BYTE, 0, + shader.setColorAttribute(bufPolyColor.glId, 4, PGL.UNSIGNED_BYTE, 0, 4 * voffset * PGL.SIZEOF_BYTE); if (lights) { - shader.setNormalAttribute(glPolyNormal, 3, PGL.FLOAT, 0, + shader.setNormalAttribute(bufPolyNormal.glId, 3, PGL.FLOAT, 0, 3 * voffset * PGL.SIZEOF_FLOAT); - shader.setAmbientAttribute(glPolyAmbient, 4, PGL.UNSIGNED_BYTE, 0, + shader.setAmbientAttribute(bufPolyAmbient.glId, 4, PGL.UNSIGNED_BYTE, 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setSpecularAttribute(glPolySpecular, 4, PGL.UNSIGNED_BYTE, 0, + shader.setSpecularAttribute(bufPolySpecular.glId, 4, PGL.UNSIGNED_BYTE, 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setEmissiveAttribute(glPolyEmissive, 4, PGL.UNSIGNED_BYTE, 0, + shader.setEmissiveAttribute(bufPolyEmissive.glId, 4, PGL.UNSIGNED_BYTE, 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setShininessAttribute(glPolyShininess, 1, PGL.FLOAT, 0, + shader.setShininessAttribute(bufPolyShininess.glId, 1, PGL.FLOAT, 0, voffset * PGL.SIZEOF_FLOAT); } if (lights || needNormals) { - shader.setNormalAttribute(glPolyNormal, 3, PGL.FLOAT, 0, + shader.setNormalAttribute(bufPolyNormal.glId, 3, PGL.FLOAT, 0, 3 * voffset * PGL.SIZEOF_FLOAT); } if (tex != null || needTexCoords) { - shader.setTexcoordAttribute(glPolyTexcoord, 2, PGL.FLOAT, 0, + shader.setTexcoordAttribute(bufPolyTexcoord.glId, 2, PGL.FLOAT, 0, 2 * voffset * PGL.SIZEOF_FLOAT); shader.setTexture(tex); } - shader.draw(glPolyIndex, icount, ioffset); + for (VertexAttribute attrib: polyAttribs.values()) { + if (!attrib.active(shader)) continue; + attrib.bind(pgl); + shader.setAttributeVBO(attrib.glLoc, attrib.buf.glId, + attrib.tessSize, attrib.type, + attrib.isColor(), 0, attrib.sizeInBytes(voffset)); + } + + shader.draw(bufPolyIndex.glId, icount, ioffset); } + for (VertexAttribute attrib: polyAttribs.values()) { + if (attrib.active(shader)) attrib.unbind(pgl); + } shader.unbind(); } unbindPolyBuffers(); } + protected void flushSortedPolys() { + boolean customShader = polyShader != null; + boolean needNormals = customShader ? polyShader.accessNormals() : false; + boolean needTexCoords = customShader ? polyShader.accessTexCoords() : false; - class Triangle { - int i0, i1, i2; - PImage tex; - float dist; - Triangle(int i0, int i1, int i2, PImage tex, float dist) { - this.i0 = i0; - this.i1 = i1; - this.i2 = i2; - this.tex = tex; - this.dist = dist; - } - } - // Adapted from Ben Van Citters code: - // http://openprocessing.org/sketch/100912 - Triangle[] sortedPolyTriangles = null; - int sortedTriangleCount = 0; - void sortTriangles() { - if (sortedPolyTriangles == null) { - sortedPolyTriangles = new Triangle[512]; - } + sorter.sort(tessGeo); - float[] vertices = tessGeo.polyVertices; - short[] indices = tessGeo.polyIndices; - float[] src0 = {0, 0, 0, 0}; - float[] src1 = {0, 0, 0, 0}; - float[] src2 = {0, 0, 0, 0}; - float[] pt0 = {0, 0, 0, 0}; - float[] pt1 = {0, 0, 0, 0}; - float[] pt2 = {0, 0, 0, 0}; - - sortedTriangleCount = 0; - for (int i = 0; i < texCache.size; i++) { - PImage textureImage = texCache.getTextureImage(i); - int first = texCache.firstCache[i]; - int last = texCache.lastCache[i]; - IndexCache cache = tessGeo.polyIndexCache; - for (int n = first; n <= last; n++) { - int ioffset = n == first ? texCache.firstIndex[i] : - cache.indexOffset[n]; - int icount = n == last ? texCache.lastIndex[i] - ioffset + 1 : - cache.indexOffset[n] + cache.indexCount[n] - - ioffset; - int voffset = cache.vertexOffset[n]; - for (int tr = ioffset / 3; tr < (ioffset + icount) / 3; tr++) { - if (sortedPolyTriangles.length == sortedTriangleCount) { - // expand array - int newSize = sortedTriangleCount << 1; - Triangle[] temp = new Triangle[newSize]; - PApplet.arrayCopy(sortedPolyTriangles, 0, temp, 0, newSize); - sortedPolyTriangles = temp; - } + int triangleCount = tessGeo.polyIndexCount / 3; + int[] texMap = sorter.texMap; + int[] voffsetMap = sorter.voffsetMap; - int i0 = voffset + indices[3 * tr + 0]; - int i1 = voffset + indices[3 * tr + 1]; - int i2 = voffset + indices[3 * tr + 2]; - PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); - PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); - PApplet.arrayCopy(vertices, 4 * i2, src2, 0, 4); - modelview.mult(src0, pt0); - modelview.mult(src1, pt1); - modelview.mult(src2, pt2); - // add all three verts together and divide... could use another determination - // of the 'depth' of the triangle such as min or max vert dist... - float[] pos = new float[]{(pt0[X] + pt1[X] + pt2[X]) /3, - (pt0[Y] + pt1[Y] + pt2[Y]) /3, - (pt0[Z] + pt1[Z] + pt2[Z]) /3}; + int[] vertexOffset = tessGeo.polyIndexCache.vertexOffset; + + updatePolyBuffers(lights, texCache.hasTextures, needNormals, needTexCoords); - // pt0, pt1 and pt2 are in eye coordinates since they have been - // multiplied by the modelview matrix. - float d = PApplet.dist(0f, 0f, 0f, pos[0], pos[1], pos[2]); + int ti = 0; - Triangle tri = new Triangle(i0, i1, i2, textureImage, d); - sortedPolyTriangles[sortedTriangleCount] = tri; - sortedTriangleCount++; - } + while (ti < triangleCount) { + + int startTi = ti; + int texId = texMap[ti]; + int voffsetId = voffsetMap[ti]; + + do { + ++ti; + } while (ti < triangleCount && + texId == texMap[ti] && + voffsetId == voffsetMap[ti]); + + int endTi = ti; + + Texture tex = texCache.getTexture(texId); + + int voffset = vertexOffset[voffsetId]; + + int ioffset = 3 * startTi; + int icount = 3 * (endTi - startTi); + + // If the renderer is 2D, then lights should always be false, + // so no need to worry about that. + PShader shader = getPolyShader(lights, tex != null); + shader.bind(); + + shader.setVertexAttribute(bufPolyVertex.glId, 4, PGL.FLOAT, 0, + 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(bufPolyColor.glId, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + + if (lights) { + shader.setNormalAttribute(bufPolyNormal.glId, 3, PGL.FLOAT, 0, + 3 * voffset * PGL.SIZEOF_FLOAT); + shader.setAmbientAttribute(bufPolyAmbient.glId, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setSpecularAttribute(bufPolySpecular.glId, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setEmissiveAttribute(bufPolyEmissive.glId, 4, PGL.UNSIGNED_BYTE, 0, + 4 * voffset * PGL.SIZEOF_BYTE); + shader.setShininessAttribute(bufPolyShininess.glId, 1, PGL.FLOAT, 0, + voffset * PGL.SIZEOF_FLOAT); } - } - quickSortTris(0, sortedTriangleCount - 1); - } - // an 'in-place' implementation of quick I whipped together late at night - // based off of the algorithm found on wikipedia: http://en.wikipedia.org/wiki/Quicksort - private void quickSortTris(int leftI, int rightI) { - if (leftI < rightI) { - int pivotIndex = (leftI + rightI)/2; - int newPivotIndex = partition(leftI,rightI,pivotIndex); - quickSortTris(leftI, newPivotIndex-1); - quickSortTris(newPivotIndex+1, rightI); - } - } + if (lights || needNormals) { + shader.setNormalAttribute(bufPolyNormal.glId, 3, PGL.FLOAT, 0, + 3 * voffset * PGL.SIZEOF_FLOAT); + } - //part of quicksort - private int partition(int leftIndex, int rightIndex, int pivotIndex) { - float pivotVal = sortedPolyTriangles[pivotIndex].dist; - swapTris(pivotIndex,rightIndex); - int storeIndex = leftIndex; - for(int i = leftIndex; i < rightIndex; i++) - { - if(sortedPolyTriangles[i].dist > pivotVal) - { - swapTris(i,storeIndex); - storeIndex++; + if (tex != null || needTexCoords) { + shader.setTexcoordAttribute(bufPolyTexcoord.glId, 2, PGL.FLOAT, 0, + 2 * voffset * PGL.SIZEOF_FLOAT); + shader.setTexture(tex); } - } - swapTris(rightIndex,storeIndex); - return storeIndex; - } - //part of quicksort - private void swapTris(int a, int b) { - Triangle tmp = sortedPolyTriangles[a]; - sortedPolyTriangles[a] = sortedPolyTriangles[b]; - sortedPolyTriangles[b] = tmp; + for (VertexAttribute attrib: polyAttribs.values()) { + if (!attrib.active(shader)) continue; + attrib.bind(pgl); + shader.setAttributeVBO(attrib.glLoc, attrib.buf.glId, + attrib.tessSize, attrib.type, + attrib.isColor(), 0, attrib.sizeInBytes(voffset)); + } + + shader.draw(bufPolyIndex.glId, icount, ioffset); + + for (VertexAttribute attrib: polyAttribs.values()) { + if (attrib.active(shader)) attrib.unbind(pgl); + } + shader.unbind(); + } + unbindPolyBuffers(); } - void rawPolys() { raw.colorMode(RGB); raw.noStroke(); raw.beginShape(TRIANGLES); - - //sortTriangles(); - float[] vertices = tessGeo.polyVertices; int[] color = tessGeo.polyColors; float[] uv = tessGeo.polyTexCoords; - short[] indices = tessGeo.polyIndices; // unused [fry] - -/* - sortTriangles(); - for (int i = 0; i < sortedTriangleCount; i++) { - Triangle tri = sortedPolyTriangles[i]; - int i0 = tri.i0; - int i1 = tri.i1; - int i2 = tri.i2; - PImage tex = tri.tex; - - float[] pt0 = {0, 0, 0, 0}; - float[] pt1 = {0, 0, 0, 0}; - float[] pt2 = {0, 0, 0, 0}; - int argb0 = PGL.nativeToJavaARGB(color[i0]); - int argb1 = PGL.nativeToJavaARGB(color[i1]); - int argb2 = PGL.nativeToJavaARGB(color[i2]); - - if (flushMode == FLUSH_CONTINUOUSLY) { - float[] src0 = {0, 0, 0, 0}; - float[] src1 = {0, 0, 0, 0}; - float[] src2 = {0, 0, 0, 0}; - PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); - PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); - PApplet.arrayCopy(vertices, 4 * i2, src2, 0, 4); - modelview.mult(src0, pt0); - modelview.mult(src1, pt1); - modelview.mult(src2, pt2); - } else { - PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); - PApplet.arrayCopy(vertices, 4 * i1, pt1, 0, 4); - PApplet.arrayCopy(vertices, 4 * i2, pt2, 0, 4); - } - - if (tex != null) { - raw.texture(tex); - if (raw.is3D()) { - raw.fill(argb0); - raw.vertex(pt0[X], pt0[Y], pt0[Z], uv[2 * i0 + 0], uv[2 * i0 + 1]); - raw.fill(argb1); - raw.vertex(pt1[X], pt1[Y], pt1[Z], uv[2 * i1 + 0], uv[2 * i1 + 1]); - raw.fill(argb2); - raw.vertex(pt2[X], pt2[Y], pt2[Z], uv[2 * i2 + 0], uv[2 * i2 + 1]); - } else if (raw.is2D()) { - float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); - float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); - float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); - float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); - float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); - float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); - raw.fill(argb0); - raw.vertex(sx0, sy0, uv[2 * i0 + 0], uv[2 * i0 + 1]); - raw.fill(argb1); - raw.vertex(sx1, sy1, uv[2 * i1 + 0], uv[2 * i1 + 1]); - raw.fill(argb1); - raw.vertex(sx2, sy2, uv[2 * i2 + 0], uv[2 * i2 + 1]); - } - } else { - if (raw.is3D()) { - raw.fill(argb0); - raw.vertex(pt0[X], pt0[Y], pt0[Z]); - raw.fill(argb1); - raw.vertex(pt1[X], pt1[Y], pt1[Z]); - raw.fill(argb2); - raw.vertex(pt2[X], pt2[Y], pt2[Z]); - } else if (raw.is2D()) { - float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); - float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); - float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); - float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); - float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); - float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); - raw.fill(argb0); - raw.vertex(sx0, sy0); - raw.fill(argb1); - raw.vertex(sx1, sy1); - raw.fill(argb2); - raw.vertex(sx2, sy2); - } - } - - } -*/ - + short[] indices = tessGeo.polyIndices; for (int i = 0; i < texCache.size; i++) { PImage textureImage = texCache.getTextureImage(i); @@ -2837,6 +2943,107 @@ void rawPolys() { } + void rawSortedPolys() { + raw.colorMode(RGB); + raw.noStroke(); + raw.beginShape(TRIANGLES); + + float[] vertices = tessGeo.polyVertices; + int[] color = tessGeo.polyColors; + float[] uv = tessGeo.polyTexCoords; + short[] indices = tessGeo.polyIndices; + + sorter.sort(tessGeo); + int[] triangleIndices = sorter.triangleIndices; + int[] texMap = sorter.texMap; + int[] voffsetMap = sorter.voffsetMap; + + int[] vertexOffset = tessGeo.polyIndexCache.vertexOffset; + + for (int i = 0; i < tessGeo.polyIndexCount/3; i++) { + int ti = triangleIndices[i]; + PImage tex = texCache.getTextureImage(texMap[ti]); + int voffset = vertexOffset[voffsetMap[ti]]; + + int i0 = voffset + indices[3*ti+0]; + int i1 = voffset + indices[3*ti+1]; + int i2 = voffset + indices[3*ti+2]; + + float[] pt0 = {0, 0, 0, 0}; + float[] pt1 = {0, 0, 0, 0}; + float[] pt2 = {0, 0, 0, 0}; + int argb0 = PGL.nativeToJavaARGB(color[i0]); + int argb1 = PGL.nativeToJavaARGB(color[i1]); + int argb2 = PGL.nativeToJavaARGB(color[i2]); + + if (flushMode == FLUSH_CONTINUOUSLY) { + float[] src0 = {0, 0, 0, 0}; + float[] src1 = {0, 0, 0, 0}; + float[] src2 = {0, 0, 0, 0}; + PApplet.arrayCopy(vertices, 4 * i0, src0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, src1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, src2, 0, 4); + modelview.mult(src0, pt0); + modelview.mult(src1, pt1); + modelview.mult(src2, pt2); + } else { + PApplet.arrayCopy(vertices, 4 * i0, pt0, 0, 4); + PApplet.arrayCopy(vertices, 4 * i1, pt1, 0, 4); + PApplet.arrayCopy(vertices, 4 * i2, pt2, 0, 4); + } + + if (tex != null) { + raw.texture(tex); + if (raw.is3D()) { + raw.fill(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z], uv[2 * i0 + 0], uv[2 * i0 + 1]); + raw.fill(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z], uv[2 * i1 + 0], uv[2 * i1 + 1]); + raw.fill(argb2); + raw.vertex(pt2[X], pt2[Y], pt2[Z], uv[2 * i2 + 0], uv[2 * i2 + 1]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + raw.fill(argb0); + raw.vertex(sx0, sy0, uv[2 * i0 + 0], uv[2 * i0 + 1]); + raw.fill(argb1); + raw.vertex(sx1, sy1, uv[2 * i1 + 0], uv[2 * i1 + 1]); + raw.fill(argb1); + raw.vertex(sx2, sy2, uv[2 * i2 + 0], uv[2 * i2 + 1]); + } + } else { + if (raw.is3D()) { + raw.fill(argb0); + raw.vertex(pt0[X], pt0[Y], pt0[Z]); + raw.fill(argb1); + raw.vertex(pt1[X], pt1[Y], pt1[Z]); + raw.fill(argb2); + raw.vertex(pt2[X], pt2[Y], pt2[Z]); + } else if (raw.is2D()) { + float sx0 = screenXImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sy0 = screenYImpl(pt0[0], pt0[1], pt0[2], pt0[3]); + float sx1 = screenXImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sy1 = screenYImpl(pt1[0], pt1[1], pt1[2], pt1[3]); + float sx2 = screenXImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + float sy2 = screenYImpl(pt2[0], pt2[1], pt2[2], pt2[3]); + raw.fill(argb0); + raw.vertex(sx0, sy0); + raw.fill(argb1); + raw.vertex(sx1, sy1); + raw.fill(argb2); + raw.vertex(sx2, sy2); + } + } + } + + raw.endShape(); + } + + protected void flushLines() { updateLineBuffers(); @@ -2849,14 +3056,14 @@ protected void flushLines() { int icount = cache.indexCount[n]; int voffset = cache.vertexOffset[n]; - shader.setVertexAttribute(glLineVertex, 4, PGL.FLOAT, 0, + shader.setVertexAttribute(bufLineVertex.glId, 4, PGL.FLOAT, 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.setColorAttribute(glLineColor, 4, PGL.UNSIGNED_BYTE, 0, + shader.setColorAttribute(bufLineColor.glId, 4, PGL.UNSIGNED_BYTE, 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setLineAttribute(glLineAttrib, 4, PGL.FLOAT, 0, + shader.setLineAttribute(bufLineAttrib.glId, 4, PGL.FLOAT, 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.draw(glLineIndex, icount, ioffset); + shader.draw(bufLineIndex.glId, icount, ioffset); } shader.unbind(); @@ -2950,14 +3157,14 @@ protected void flushPoints() { int icount = cache.indexCount[n]; int voffset = cache.vertexOffset[n]; - shader.setVertexAttribute(glPointVertex, 4, PGL.FLOAT, 0, + shader.setVertexAttribute(bufPointVertex.glId, 4, PGL.FLOAT, 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.setColorAttribute(glPointColor, 4, PGL.UNSIGNED_BYTE, 0, + shader.setColorAttribute(bufPointColor.glId, 4, PGL.UNSIGNED_BYTE, 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setPointAttribute(glPointAttrib, 2, PGL.FLOAT, 0, + shader.setPointAttribute(bufPointAttrib.glId, 2, PGL.FLOAT, 0, 2 * voffset * PGL.SIZEOF_FLOAT); - shader.draw(glPointIndex, icount, ioffset); + shader.draw(bufPointIndex.glId, icount, ioffset); } shader.unbind(); @@ -3251,7 +3458,6 @@ protected void arcImpl(float x, float y, float w, float h, normalMode = NORMAL_MODE_SHAPE; inGeo.setMaterial(fillColor, strokeColor, strokeWeight, ambientColor, specularColor, emissiveColor, shininess); - inGeo.setNormal(normalX, normalY, normalZ); inGeo.addArc(x, y, w, h, start, stop, fill, stroke, mode); endShape(); @@ -3363,7 +3569,7 @@ public void sphere(float r) { // SMOOTH - +/* @Override public void smooth() { if (quality < 2) { @@ -3399,13 +3605,16 @@ public void smooth(int level) { quality = level; - if (quality == 1) { + if (quality <= 1) { quality = 0; + textureSampling = Texture.POINT; + } else { + textureSampling = Texture.TRILINEAR; } // This will trigger a surface restart next time // requestDraw() is called. - restartPGL(); +// restartPGL(); } } @@ -3415,6 +3624,7 @@ public void noSmooth() { if (smoothDisabled) return; smooth = false; + textureSampling = Texture.POINT; if (1 < quality) { smoothCallCount++; @@ -3431,6 +3641,7 @@ public void noSmooth() { restartPGL(); } } + */ ////////////////////////////////////////////////////////////// @@ -3560,6 +3771,189 @@ protected boolean textModeCheck(int mode) { // TEXT IMPL + @Override + public void text(char c, float x, float y) { + if (textFont == null) { + defaultFontOrDeath("text"); + } + + int sign = cameraUp ? -1 : +1; + + if (textAlignY == CENTER) { + y += sign * textAscent() / 2; + } else if (textAlignY == TOP) { + y += sign * textAscent(); + } else if (textAlignY == BOTTOM) { + y -= sign * textDescent(); + //} else if (textAlignY == BASELINE) { + // do nothing + } + + textBuffer[0] = c; + textLineAlignImpl(textBuffer, 0, 1, x, y); + } + + + @Override + public void text(String str, float x, float y) { + if (textFont == null) { + defaultFontOrDeath("text"); + } + + int sign = cameraUp ? -1 : +1; + + int length = str.length(); + if (length > textBuffer.length) { + textBuffer = new char[length + 10]; + } + str.getChars(0, length, textBuffer, 0); + + // If multiple lines, sum the height of the additional lines + float high = 0; //-textAscent(); + for (int i = 0; i < length; i++) { + if (textBuffer[i] == '\n') { + high += sign * textLeading; + } + } + if (textAlignY == CENTER) { + // for a single line, this adds half the textAscent to y + // for multiple lines, subtract half the additional height + //y += (textAscent() - textDescent() - high)/2; + y += sign * (textAscent() - high)/2; + } else if (textAlignY == TOP) { + // for a single line, need to add textAscent to y + // for multiple lines, no different + y += sign * textAscent(); + } else if (textAlignY == BOTTOM) { + // for a single line, this is just offset by the descent + // for multiple lines, subtract leading for each line + y -= sign * textDescent() + high; + } + + int start = 0; + int index = 0; + while (index < length) { + if (textBuffer[index] == '\n') { + textLineAlignImpl(textBuffer, start, index, x, y); + start = index + 1; + y += sign * textLeading; + } + index++; + } + if (start < length) { + textLineAlignImpl(textBuffer, start, index, x, y); + } + } + + + @Override + public void text(String str, float x1, float y1, float x2, float y2) { + if (textFont == null) { + defaultFontOrDeath("text"); + } + + int sign = cameraUp ? -1 : +1; + + float hradius, vradius; + switch (rectMode) { + case CORNER: + x2 += x1; y2 += y1; + break; + case RADIUS: + hradius = x2; + vradius = y2; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + break; + case CENTER: + hradius = x2 / 2.0f; + vradius = y2 / 2.0f; + x2 = x1 + hradius; + y2 = y1 + vradius; + x1 -= hradius; + y1 -= vradius; + } + if (x2 < x1) { + float temp = x1; x1 = x2; x2 = temp; + } + if (y2 < y1) { + float temp = y1; y1 = y2; y2 = temp; + } + + float boxWidth = x2 - x1; + + float spaceWidth = textWidth(' '); + + if (textBreakStart == null) { + textBreakStart = new int[20]; + textBreakStop = new int[20]; + } + textBreakCount = 0; + + int length = str.length(); + if (length + 1 > textBuffer.length) { + textBuffer = new char[length + 1]; + } + str.getChars(0, length, textBuffer, 0); + // add a fake newline to simplify calculations + textBuffer[length++] = '\n'; + + int sentenceStart = 0; + for (int i = 0; i < length; i++) { + if (textBuffer[i] == '\n') { + boolean legit = + textSentence(textBuffer, sentenceStart, i, boxWidth, spaceWidth); + if (!legit) break; + sentenceStart = i + 1; + } + } + + // lineX is the position where the text starts, which is adjusted + // to left/center/right based on the current textAlign + float lineX = x1; //boxX1; + if (textAlign == CENTER) { + lineX = lineX + boxWidth/2f; + } else if (textAlign == RIGHT) { + lineX = x2; //boxX2; + } + + float boxHeight = y2 - y1; + // incorporate textAscent() for the top (baseline will be y1 + ascent) + // and textDescent() for the bottom, so that lower parts of letters aren't + // outside the box. [0151] + float topAndBottom = textAscent() + textDescent(); + int lineFitCount = 1 + PApplet.floor((boxHeight - topAndBottom) / textLeading); + int lineCount = Math.min(textBreakCount, lineFitCount); + + if (textAlignY == CENTER) { + float lineHigh = textAscent() + textLeading * (lineCount - 1); + float y = cameraUp ? y2 - textAscent() - (boxHeight - lineHigh) / 2 : + y1 + textAscent() + (boxHeight - lineHigh) / 2; + for (int i = 0; i < lineCount; i++) { + textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y); + y += sign * textLeading; + } + + } else if (textAlignY == BOTTOM) { + float y = cameraUp ? y1 + textDescent() + textLeading * (lineCount - 1) : + y2 - textDescent() - textLeading * (lineCount - 1); + for (int i = 0; i < lineCount; i++) { + textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y); + y += sign * textLeading; + } + + } else { // TOP or BASELINE just go to the default + float y = cameraUp ? y2 - textAscent() : y1 + textAscent(); + for (int i = 0; i < lineCount; i++) { + textLineAlignImpl(textBuffer, textBreakStart[i], textBreakStop[i], lineX, y); + y += sign * textLeading; + } + } + } + + @Override public float textAscent() { if (textFont == null) defaultFontOrDeath("textAscent"); @@ -3573,7 +3967,7 @@ public float textAscent() { @Override public float textDescent() { - if (textFont == null) defaultFontOrDeath("textAscent"); + if (textFont == null) defaultFontOrDeath("textDescent"); Object font = textFont.getNative(); float descent = 0; if (font != null) descent = pgl.getFontDescent(font); @@ -3593,14 +3987,13 @@ protected float textWidthImpl(char buffer[], int start, int stop) { @Override - public void textSize(float size) { - if (textFont == null) defaultFontOrDeath("textSize", size); + protected void handleTextSize(float size) { Object font = textFont.getNative(); if (font != null) { Object dfont = pgl.getDerivedFont(font, size); - textFont.setNative(dfont); + if (dfont != null) textFont.setNative(dfont); } - super.textSize(size); + super.handleTextSize(size); } @@ -3682,10 +4075,14 @@ protected void textCharImpl(char ch, float x, float y) { float lextent = glyph.leftExtent / (float) textFont.getSize(); float textent = glyph.topExtent / (float) textFont.getSize(); + // The default text setting assumes an Y axis pointing down, so + // inverting in the the case Y points up + int sign = cameraUp ? -1 : +1; + float x1 = x + lextent * textSize; - float y1 = y - textent * textSize; + float y1 = y - sign * textent * textSize; float x2 = x1 + bwidth * textSize; - float y2 = y1 + high * textSize; + float y2 = y1 + sign * high * textSize; textCharModelImpl(tinfo, x1, y1, x2, y2); } else if (textMode == SHAPE) { @@ -3698,11 +4095,8 @@ protected void textCharImpl(char ch, float x, float y) { protected void textCharModelImpl(FontTexture.TextureInfo info, float x0, float y0, float x1, float y1) { - if (textTex.currentTex != info.texIndex) { - textTex.setTexture(info.texIndex); - } beginShape(QUADS); - texture(textTex.getCurrentTexture()); + texture(textTex.getTexture(info)); vertex(x0, y0, info.u0, info.v0); vertex(x1, y0, info.u1, info.v0); vertex(x1, y1, info.u1, info.v1); @@ -3861,9 +4255,40 @@ protected void translateImpl(float tx, float ty, float tz) { static protected void invTranslate(PMatrix3D matrix, float tx, float ty, float tz) { matrix.preApply(1, 0, 0, -tx, - 0, 1, 0, -ty, - 0, 0, 1, -tz, - 0, 0, 0, 1); + 0, 1, 0, -ty, + 0, 0, 1, -tz, + 0, 0, 0, 1); + } + + + static protected void invTranslate(PMatrix2D matrix, + float tx, float ty) { + matrix.preApply(1, 0, -tx, + 0, 1, -ty); + } + + + static protected float matrixScale(PMatrix matrix) { + // Volumetric scaling factor that is associated to the given + // transformation matrix, which is given by the absolute value of its + // determinant: + float factor = 1; + + if (matrix != null) { + if (matrix instanceof PMatrix2D) { + PMatrix2D tr = (PMatrix2D)matrix; + float areaScaleFactor = Math.abs(tr.m00 * tr.m11 - tr.m01 * tr.m10); + factor = (float) Math.sqrt(areaScaleFactor); + } else if (matrix instanceof PMatrix3D) { + PMatrix3D tr = (PMatrix3D)matrix; + float volumeScaleFactor = + Math.abs(tr.m00 * (tr.m11 * tr.m22 - tr.m12 * tr.m21) + + tr.m01 * (tr.m12 * tr.m20 - tr.m10 * tr.m22) + + tr.m02 * (tr.m10 * tr.m21 - tr.m11 * tr.m20)); + factor = (float) Math.pow(volumeScaleFactor, 1.0f / 3.0f); + } + } + return factor; } @@ -3928,16 +4353,21 @@ protected void rotateImpl(float angle, float v0, float v1, float v2) { } - static private void invRotate(PMatrix3D matrix, float angle, - float v0, float v1, float v2) { + static protected void invRotate(PMatrix3D matrix, float angle, + float v0, float v1, float v2) { float c = PApplet.cos(-angle); float s = PApplet.sin(-angle); float t = 1.0f - c; matrix.preApply((t*v0*v0) + c, (t*v0*v1) - (s*v2), (t*v0*v2) + (s*v1), 0, - (t*v0*v1) + (s*v2), (t*v1*v1) + c, (t*v1*v2) - (s*v0), 0, - (t*v0*v2) - (s*v1), (t*v1*v2) + (s*v0), (t*v2*v2) + c, 0, - 0, 0, 0, 1); + (t*v0*v1) + (s*v2), (t*v1*v1) + c, (t*v1*v2) - (s*v0), 0, + (t*v0*v2) - (s*v1), (t*v1*v2) + (s*v0), (t*v2*v2) + c, 0, + 0, 0, 0, 1); + } + + + static protected void invRotate(PMatrix2D matrix, float angle) { + matrix.rotate(-angle); } @@ -3982,6 +4412,11 @@ static protected void invScale(PMatrix3D matrix, float x, float y, float z) { } + static protected void invScale(PMatrix2D matrix, float x, float y) { + matrix.preApply(1/x, 0, 0, 0, 1/y, 0); + } + + @Override public void shearX(float angle) { float t = (float) Math.tan(angle); @@ -4372,7 +4807,8 @@ public void endCamera() { */ @Override public void camera() { - camera(cameraX, cameraY, cameraZ, cameraX, cameraY, 0, 0, 1, 0); + camera(defCameraX, defCameraY, defCameraZ, defCameraX, defCameraY, + 0, 0, 1, 0); } @@ -4436,30 +4872,43 @@ public void camera() { public void camera(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ) { + cameraX = eyeX; + cameraY = eyeY; + cameraZ = eyeZ; + // Calculating Z vector float z0 = eyeX - centerX; float z1 = eyeY - centerY; float z2 = eyeZ - centerZ; - float mag = PApplet.sqrt(z0 * z0 + z1 * z1 + z2 * z2); - if (nonZero(mag)) { - z0 /= mag; - z1 /= mag; - z2 /= mag; + eyeDist = PApplet.sqrt(z0 * z0 + z1 * z1 + z2 * z2); + if (nonZero(eyeDist)) { + z0 /= eyeDist; + z1 /= eyeDist; + z2 /= eyeDist; } - cameraEyeX = eyeX; - cameraEyeY = eyeY; - cameraEyeZ = eyeZ; + + forwardX = z0; + forwardY = z1; + forwardZ = z2; // Calculating Y vector float y0 = upX; float y1 = upY; float y2 = upZ; + this.upX = upX; + this.upY = upY; + this.upZ = upZ; + // Computing X vector as Y cross Z float x0 = y1 * z2 - y2 * z1; float x1 = -y0 * z2 + y2 * z0; float x2 = y0 * z1 - y1 * z0; + rightX = x0; + rightY = x1; + rightZ = x2; + // Recompute Y = Z cross X y0 = z1 * x2 - z2 * x1; y1 = -z0 * x2 + z2 * x0; @@ -4467,18 +4916,18 @@ public void camera(float eyeX, float eyeY, float eyeZ, // Cross product gives area of parallelogram, which is < 1.0 for // non-perpendicular unit-length vectors; so normalize x, y here: - mag = PApplet.sqrt(x0 * x0 + x1 * x1 + x2 * x2); - if (nonZero(mag)) { - x0 /= mag; - x1 /= mag; - x2 /= mag; + float xmag = PApplet.sqrt(x0 * x0 + x1 * x1 + x2 * x2); + if (nonZero(xmag)) { + x0 /= xmag; + x1 /= xmag; + x2 /= xmag; } - mag = PApplet.sqrt(y0 * y0 + y1 * y1 + y2 * y2); - if (nonZero(mag)) { - y0 /= mag; - y1 /= mag; - y2 /= mag; + float ymag = PApplet.sqrt(y0 * y0 + y1 * y1 + y2 * y2); + if (nonZero(ymag)) { + y0 /= ymag; + y1 /= ymag; + y2 /= ymag; } modelview.set(x0, x1, x2, 0, @@ -4491,9 +4940,16 @@ public void camera(float eyeX, float eyeY, float eyeZ, float tz = -eyeZ; modelview.translate(tx, ty, tz); - modelviewInv.set(modelview); - modelviewInv.invert(); - + // The initial modelview transformation can be decomposed in a orthogonal + // matrix (with the inverse simply being the transpose), and a translation. + // The modelview inverse can then be calculated as follows, without need of + // employing the more general, slower, inverse() calculation. + modelviewInv.set(x0, y0, z0, 0, + x1, y1, z1, 0, + x2, y2, z2, 0, + 0, 0, 0, 1); + modelviewInv.translate(-tx, -ty, -tz); + camera.set(modelview); cameraInv.set(modelviewInv); @@ -4510,6 +4966,12 @@ public void printCamera() { } + @Override + public void cameraUp() { + cameraUp = true; + } + + protected void defaultCamera() { camera(); } @@ -4526,7 +4988,7 @@ protected void defaultCamera() { */ @Override public void ortho() { - ortho(0, width, 0, height, 0, cameraEyeZ * 10); + ortho(-width/2f, width/2f, -height/2f, height/2f, 0, eyeDist * 10); } @@ -4537,7 +4999,7 @@ public void ortho() { @Override public void ortho(float left, float right, float bottom, float top) { - ortho(left, right, bottom, top, 0, cameraEyeZ * 10); + ortho(left, right, bottom, top, 0, eyeDist * 10); } @@ -4553,13 +5015,6 @@ public void ortho(float left, float right, float h = top - bottom; float d = far - near; - // Applying the camera translation (only on x and y, as near and far - // are given as distances from the viewer) - left -= cameraEyeX; - right -= cameraEyeX; - bottom -= cameraEyeY; - top -= cameraEyeY; - // Flushing geometry with a different perspective configuration. flush(); @@ -4603,7 +5058,7 @@ public void ortho(float left, float right, */ @Override public void perspective() { - perspective(cameraFOV, cameraAspect, cameraNear, cameraFar); + perspective(defCameraFOV, defCameraAspect, defCameraNear, defCameraFar); } @@ -4632,6 +5087,11 @@ public void frustum(float left, float right, float bottom, float top, // Flushing geometry with a different perspective configuration. flush(); + cameraFOV = 2 * (float) Math.atan2(top, znear); + cameraAspect = left / bottom; + cameraNear = znear; + cameraFar = zfar; + float n2 = 2 * znear; float w = right - left; float h = top - bottom; @@ -5206,18 +5666,14 @@ public void lightSpecular(float x, float y, float z) { protected void enableLighting() { - if (!lights) { - flush(); // Flushing non-lit geometry. - lights = true; - } + flush(); + lights = true; } protected void disableLighting() { - if (lights) { - flush(); // Flushing lit geometry. - lights = false; - } + flush(); + lights = false; } @@ -5230,8 +5686,8 @@ protected void lightPosition(int num, float x, float y, float z, lightPosition[4 * num + 2] = x*modelview.m20 + y*modelview.m21 + z*modelview.m22 + modelview.m23; - // Used to inicate if the light is directional or not. - lightPosition[4 * num + 3] = dir ? 1: 0; + // Used to indicate if the light is directional or not. + lightPosition[4 * num + 3] = dir ? 0 : 1; } @@ -5245,10 +5701,17 @@ protected void lightNormal(int num, float dx, float dy, float dz) { float nz = dx*modelviewInv.m02 + dy*modelviewInv.m12 + dz*modelviewInv.m22; - float invn = 1.0f / PApplet.dist(0, 0, 0, nx, ny, nz); - lightNormal[3 * num + 0] = invn * nx; - lightNormal[3 * num + 1] = invn * ny; - lightNormal[3 * num + 2] = invn * nz; + float d = PApplet.dist(0, 0, 0, nx, ny, nz); + if (0 < d) { + float invn = 1.0f / d; + lightNormal[3 * num + 0] = invn * nx; + lightNormal[3 * num + 1] = invn * ny; + lightNormal[3 * num + 2] = invn * nz; + } else { + lightNormal[3 * num + 0] = 0; + lightNormal[3 * num + 1] = 0; + lightNormal[3 * num + 2] = 0; + } } @@ -5331,31 +5794,21 @@ protected void noLightSpot(int num) { protected void backgroundImpl(PImage image) { backgroundImpl(); set(0, 0, image); - if (0 < parent.frameCount) { - clearColorBuffer = true; - } // Setting the background as opaque. If this an offscreen surface, the // alpha channel will be set to 1 in endOffscreenDraw(), even if // blending operations during draw create translucent areas in the // color buffer. backgroundA = 1; + loaded = false; } @Override protected void backgroundImpl() { flush(); - - if (!hints[DISABLE_DEPTH_MASK]) { - pgl.clearDepth(1); - pgl.clear(PGL.DEPTH_BUFFER_BIT); - } - - pgl.clearColor(backgroundR, backgroundG, backgroundB, backgroundA); - pgl.clear(PGL.COLOR_BUFFER_BIT); - if (0 < parent.frameCount) { - clearColorBuffer = true; - } + pgl.clearBackground(backgroundR, backgroundG, backgroundB, backgroundA, + !hints[DISABLE_DEPTH_MASK], true); + loaded = false; } @@ -5438,7 +5891,7 @@ public boolean isGL() { // color buffer into it. @Override public void loadPixels() { - if (primarySurface && sized) { + if (primaryGraphics && sized) { // Something wrong going on with threading, sized can never be true if // all the steps in a resize happen inside the Animation thread. return; @@ -5450,7 +5903,7 @@ public void loadPixels() { needEndDraw = true; } - if (!arePixelsUpToDate) { + if (!loaded) { // Draws any remaining geometry in case the user is still not // setting/getting new pixels. flush(); @@ -5458,12 +5911,13 @@ public void loadPixels() { allocatePixels(); - if (!arePixelsUpToDate) { + if (!loaded) { readPixels(); } // Pixels are now up-to-date, set the flag. - arePixelsUpToDate = true; + loaded = true; + if (needEndDraw) { endDraw(); @@ -5472,25 +5926,17 @@ public void loadPixels() { protected void allocatePixels() { - if ((pixels == null) || (pixels.length != width * height)) { - pixels = new int[width * height]; + updatePixelSize(); + if ((pixels == null) || (pixels.length != pixelWidth * pixelHeight)) { + pixels = new int[pixelWidth * pixelHeight]; pixelBuffer = PGL.allocateIntBuffer(pixels); + loaded = false; } } - protected void saveSurfaceToPixels() { - allocatePixels(); - readPixels(); - } - - - protected void restoreSurfaceFromPixels() { - drawPixels(0, 0, width, height); - } - - protected void readPixels() { + updatePixelSize(); beginPixelsOp(OP_READ); try { // The readPixelsImpl() call in inside a try/catch block because it appears @@ -5498,7 +5944,7 @@ protected void readPixels() { // thread instead of the Animation thread right after a resize. Because // of this the width and height might have a different size than the // one of the pixels arrays. - pgl.readPixelsImpl(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, + pgl.readPixelsImpl(0, 0, pixelWidth, pixelHeight, PGL.RGBA, PGL.UNSIGNED_BYTE, pixelBuffer); } catch (IndexOutOfBoundsException e) { // Silently catch the exception. @@ -5507,14 +5953,20 @@ protected void readPixels() { try { // Idem... PGL.getIntArray(pixelBuffer, pixels); - PGL.nativeToJavaARGB(pixels, width, height); + PGL.nativeToJavaARGB(pixels, pixelWidth, pixelHeight); } catch (ArrayIndexOutOfBoundsException e) { } } protected void drawPixels(int x, int y, int w, int h) { - int len = w * h; + drawPixels(pixels, x, y, w, h); + } + + + protected void drawPixels(int[] pixBuffer, int x, int y, int w, int h) { + int f = (int)pgl.getPixelScale(); + int len = f * w * f * h; if (nativePixels == null || nativePixels.length < len) { nativePixels = new int[len]; nativePixelBuffer = PGL.allocateIntBuffer(nativePixels); @@ -5525,31 +5977,32 @@ protected void drawPixels(int x, int y, int w, int h) { // The pixels to be copied to the texture need to be consecutive, and // they are not in the pixels array, so putting each row one after // another in nativePixels. - int offset0 = y * width + x; + int offset0 = f * (y * width + x); int offset1 = 0; - for (int yc = y; yc < y + h; yc++) { - System.arraycopy(pixels, offset0, nativePixels, offset1, w); - offset0 += width; - offset1 += w; + for (int yc = f * y; yc < f * (y + h); yc++) { + System.arraycopy(pixBuffer, offset0, nativePixels, offset1, f * w); + offset0 += f * width; + offset1 += f * w; } } else { - PApplet.arrayCopy(pixels, 0, nativePixels, 0, len); + PApplet.arrayCopy(pixBuffer, 0, nativePixels, 0, len); } - PGL.javaToNativeARGB(nativePixels, w, h); + PGL.javaToNativeARGB(nativePixels, f * w, f * h); } catch (ArrayIndexOutOfBoundsException e) { } PGL.putIntArray(nativePixelBuffer, nativePixels); // Copying pixel buffer to screen texture... - if (primarySurface && !pgl.isFBOBacked()) { + if (primaryGraphics && !pgl.isFBOBacked()) { // First making sure that the screen texture is valid. Only in the case // of non-FBO-backed primary surface we might need to create the texture. loadTextureImpl(POINT, false); } - boolean needToDrawTex = primarySurface && (!pgl.isFBOBacked() || + boolean needToDrawTex = primaryGraphics && (!pgl.isFBOBacked() || (pgl.isFBOBacked() && pgl.isMultisampled())) || offscreenMultisample; + if (texture == null) return; if (needToDrawTex) { // The texture to screen needs to be drawn only if we are on the primary // surface w/out FBO-layer, or with FBO-layer and multisampling. Or, we @@ -5559,23 +6012,130 @@ protected void drawPixels(int x, int y, int w, int h) { // (off)screen buffer. // First, copy the pixels to the texture. We don't need to invert the // pixel copy because the texture will be drawn inverted. - int tw = PApplet.min(texture.glWidth - x, w); - int th = PApplet.min(texture.glHeight - y, h); + int tw = PApplet.min(texture.glWidth - f * x, f * w); + int th = PApplet.min(texture.glHeight - f * y, f * h); pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, - x, y, tw, th, nativePixelBuffer); + f * x, f * y, tw, th, nativePixelBuffer); beginPixelsOp(OP_WRITE); drawTexture(x, y, w, h); endPixelsOp(); } else { // We only need to copy the pixels to the back texture where we are - // currently drawing to. Because the texture is invertex along Y, we + // currently drawing to. Because the texture is inverted along Y, we // need to reflect that in the vertical arguments. pgl.copyToTexture(texture.glTarget, texture.glFormat, texture.glName, - x, height - (y + h), w, h, nativePixelBuffer); + f * x, f * (height - (y + h)), f * w, f * h, nativePixelBuffer); + } + } + + + @Override + protected void clearState() { + super.clearState(); + if (restoreFilename != null) { + File cacheFile = new File(restoreFilename); + cacheFile.delete(); + } + } + + + @Override + protected void saveState() { + super.saveState(); + + // Queue the pixel read operation so it is performed when the surface is ready + pgl.queueEvent(new Runnable() { + @Override + public void run() { + Context context = parent.getContext(); + if (context == null || parent.getSurface().getComponent().isService()) return; + try { + restoreWidth = pixelWidth; + restoreHeight = pixelHeight; + + int[] restorePixels = new int[restoreWidth * restoreHeight]; + IntBuffer buf = IntBuffer.wrap(restorePixels); + buf.position(0); + beginPixelsOp(OP_READ); + pgl.readPixelsImpl(0, 0, pixelWidth, pixelHeight, PGL.RGBA, PGL.UNSIGNED_BYTE, buf); + endPixelsOp(); + + // Tries to use external but if not mounted, falls back on internal storage, as shown in + // https://developer.android.com/topic/performance/graphics/cache-bitmap#java + File cacheDir = Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !isExternalStorageRemovable() ? + context.getExternalCacheDir() : context.getCacheDir(); + File cacheFile = new File(cacheDir + File.separator + "restore_pixels"); + restoreFilename = cacheFile.getAbsolutePath(); + + FileOutputStream stream = new FileOutputStream(cacheFile); + ObjectOutputStream dout = new ObjectOutputStream(stream); + dout.writeObject(restorePixels); + dout.flush(); + stream.getFD().sync(); + stream.close(); + } catch (Exception ex) { + PGraphics.showWarning("Could not save screen contents to cache"); + ex.printStackTrace(); + } + } + }); + } + + + @Override + protected void restoreSurface() { + if (changed) { + changed = false; + if (restoreFilename != null && restoreWidth == pixelWidth && restoreHeight == pixelHeight) { + // Set restore count to 2 so it draws the bitmap two frames after surface change, otherwise + // the restoration does not work because the OpenGL renderer sometimes resizes the surface + // twice after restoring the app to the foreground... this may be due to broken graphics + // drivers, hacks in the GLSurfaceView class from the Replica Island game point to that, + // although those seem to be quite old: + // https://gamedev.stackexchange.com/questions/12629/workaround-to-losing-the-opengl-context-when-android-pauses + // "It fails in a very specific case: when the EGL context is lost due to resource constraints, + // and then recreated, if GL commands are sent within two frames of the surface being created + // then eglSwapBuffers() will hang." + // However, the same number showing up makes me thing this issue continue to exist to this day. + restoreCount = 2; + } + } else if (restoreCount > 0) { + restoreCount--; + if (restoreCount == 0) { + Context context = parent.getContext(); + if (context == null) return; + try { + // Load cached pixels and draw + File cacheFile = new File(restoreFilename); + FileInputStream inStream = new FileInputStream(cacheFile); + ObjectInputStream din = new ObjectInputStream(inStream); + int[] restorePixels = (int[]) din.readObject(); + if (restorePixels.length == pixelWidth * pixelHeight) { + PGL.nativeToJavaARGB(restorePixels, pixelWidth, pixelHeight); + drawPixels(restorePixels, 0, 0, pixelWidth, pixelHeight); + } + inStream.close(); + cacheFile.delete(); + } catch (Exception ex) { + PGraphics.showWarning("Could not restore screen contents from cache"); + ex.printStackTrace(); + } finally { + restoreFilename = null; + restoreWidth = -1; + restoreHeight = -1; + restoredSurface = true; + } + } } + super.restoreSurface(); } + @Override + protected boolean requestNoLoop() { + return true; + } + ////////////////////////////////////////////////////////////// // GET/SET PIXELS @@ -5610,20 +6170,264 @@ protected void setImpl(PImage sourceImage, int sourceX, int sourceY, int sourceWidth, int sourceHeight, int targetX, int targetY) { - loadPixels(); - super.setImpl(sourceImage, sourceX, sourceY, sourceWidth, sourceHeight, - targetX, targetY); - // do we need this? - // see https://github.com/processing/processing/issues/2125 -// if (sourceImage.format == RGB) { -// int targetOffset = targetY * width + targetX; -// for (int y = sourceY; y < sourceY + sourceHeight; y++) { -// for (int x = targetOffset; x < targetOffset + sourceWidth; x++) { -// pixels[x] |= 0xff000000; -// } -// targetOffset += width; -// } -// } + updatePixelSize(); + + if (sourceImage.pixels == null) { + // Copies the pixels + loadPixels(); + sourceImage.loadPixels(); + int sourceOffset = sourceY * sourceImage.pixelWidth + sourceX; + int targetOffset = targetY * pixelWidth + targetX; + for (int y = sourceY; y < sourceY + sourceHeight; y++) { + System.arraycopy(sourceImage.pixels, sourceOffset, pixels, targetOffset, sourceWidth); + sourceOffset += sourceImage.pixelWidth; + targetOffset += pixelWidth; + } + } + + // Draws the texture, copy() is very efficient because it simply renders + // the texture cache of sourceImage using OpenGL. + copy(sourceImage, + sourceX, sourceY, sourceWidth, sourceHeight, + targetX, targetY, sourceWidth, sourceHeight); + } + + + ////////////////////////////////////////////////////////////// + + // SAVE + + + @Override + public boolean save(String filename) { + return saveImpl(filename); + } + + + @Override + protected void processImageBeforeAsyncSave(PImage image) { + if (image.format == AsyncPixelReader.OPENGL_NATIVE) { + PGL.nativeToJavaARGB(image.pixels, image.width, image.height); + image.format = ARGB; + } else if (image.format == AsyncPixelReader.OPENGL_NATIVE_OPAQUE) { + PGL.nativeToJavaRGB(image.pixels, image.width, image.height); + image.format = RGB; + } + } + + + protected static void completeFinishedPixelTransfers() { + ongoingPixelTransfersIterable.addAll(ongoingPixelTransfers); + for (PGraphicsOpenGL.AsyncPixelReader pixelReader : + ongoingPixelTransfersIterable) { + // if the getter was not called this frame, + // tell it to check for completed transfers now + if (!pixelReader.calledThisFrame) { + pixelReader.completeFinishedTransfers(); + } + pixelReader.calledThisFrame = false; + } + ongoingPixelTransfersIterable.clear(); + } + + protected static void completeAllPixelTransfers() { + ongoingPixelTransfersIterable.addAll(ongoingPixelTransfers); + for (PGraphicsOpenGL.AsyncPixelReader pixelReader : + ongoingPixelTransfersIterable) { + pixelReader.completeAllTransfers(); + } + ongoingPixelTransfersIterable.clear(); + } + + + protected class AsyncPixelReader { + + // PImage formats used internally to offload + // color format conversion to save threads + static final int OPENGL_NATIVE = -1; + static final int OPENGL_NATIVE_OPAQUE = -2; + + static final int BUFFER_COUNT = 3; + + int[] pbos; + long[] fences; + String[] filenames; + int[] widths; + int[] heights; + + int head; + int tail; + int size; + + boolean supportsAsyncTransfers; + + boolean calledThisFrame; + + + /// PGRAPHICS API ////////////////////////////////////////////////////////// + + public AsyncPixelReader() { + supportsAsyncTransfers = pgl.hasPBOs() && pgl.hasSynchronization(); + if (supportsAsyncTransfers) { + pbos = new int[BUFFER_COUNT]; + fences = new long[BUFFER_COUNT]; + filenames = new String[BUFFER_COUNT]; + widths = new int[BUFFER_COUNT]; + heights = new int[BUFFER_COUNT]; + + IntBuffer intBuffer = PGL.allocateIntBuffer(BUFFER_COUNT); + intBuffer.rewind(); + pgl.genBuffers(BUFFER_COUNT, intBuffer); + for (int i = 0; i < BUFFER_COUNT; i++) { + pbos[i] = intBuffer.get(i); + } + } + } + + + public void dispose() { + if (fences != null) { + while (size > 0) { + pgl.deleteSync(fences[tail]); + size--; + tail = (tail + 1) % BUFFER_COUNT; + } + fences = null; + } + if (pbos != null) { + for (int i = 0; i < BUFFER_COUNT; i++) { + IntBuffer intBuffer = PGL.allocateIntBuffer(pbos); + pgl.deleteBuffers(BUFFER_COUNT, intBuffer); + } + pbos = null; + } + filenames = null; + widths = null; + heights = null; + size = 0; + head = 0; + tail = 0; + calledThisFrame = false; + ongoingPixelTransfers.remove(this); + } + + + public void readAndSaveAsync(final String filename) { + if (size > 0) { + boolean shouldRead = (size == BUFFER_COUNT); + if (!shouldRead) shouldRead = isLastTransferComplete(); + if (shouldRead) endTransfer(); + } else { + ongoingPixelTransfers.add(this); + } + beginTransfer(filename); + calledThisFrame = true; + } + + + public void completeFinishedTransfers() { + if (size <= 0 || !asyncImageSaver.hasAvailableTarget()) return; + + boolean needEndDraw = false; + if (!drawing) { + beginDraw(); + needEndDraw = true; + } + + while (asyncImageSaver.hasAvailableTarget() && + isLastTransferComplete()) { + endTransfer(); + } + + // make sure to always unregister if there are no ongoing transfers + // so that PGraphics can be GC'd if needed + if (size <= 0) ongoingPixelTransfers.remove(this); + + if (needEndDraw) endDraw(); + } + + + protected void completeAllTransfers() { + if (size <= 0) return; + + boolean needEndDraw = false; + if (!drawing) { + beginDraw(); + needEndDraw = true; + } + + while (size > 0) { + endTransfer(); + } + + // make sure to always unregister if there are no ongoing transfers + // so that PGraphics can be GC'd if needed + ongoingPixelTransfers.remove(this); + + if (needEndDraw) endDraw(); + } + + + /// TRANSFERS ////////////////////////////////////////////////////////////// + + public boolean isLastTransferComplete() { + if (size <= 0) return false; + int status = pgl.clientWaitSync(fences[tail], 0, 0); + return (status == PGL.ALREADY_SIGNALED) || + (status == PGL.CONDITION_SATISFIED); + } + + + public void beginTransfer(String filename) { + // check the size of the buffer + if (widths[head] != pixelWidth || heights[head] != pixelHeight) { + if (widths[head] * heights[head] != pixelWidth * pixelHeight) { + pgl.bindBuffer(PGL.PIXEL_PACK_BUFFER, pbos[head]); + pgl.bufferData(PGL.PIXEL_PACK_BUFFER, + Integer.SIZE/8 * pixelWidth * pixelHeight, + null, PGL.STREAM_READ); + } + widths[head] = pixelWidth; + heights[head] = pixelHeight; + pgl.bindBuffer(PGL.PIXEL_PACK_BUFFER, 0); + } + + pgl.bindBuffer(PGL.PIXEL_PACK_BUFFER, pbos[head]); + pgl.readPixels(0, 0, pixelWidth, pixelHeight, PGL.RGBA, PGL.UNSIGNED_BYTE, 0); + pgl.bindBuffer(PGL.PIXEL_PACK_BUFFER, 0); + + fences[head] = pgl.fenceSync(PGL.SYNC_GPU_COMMANDS_COMPLETE, 0); + filenames[head] = filename; + + head = (head + 1) % BUFFER_COUNT; + size++; + } + + + public void endTransfer() { + pgl.deleteSync(fences[tail]); + pgl.bindBuffer(PGL.PIXEL_PACK_BUFFER, pbos[tail]); + ByteBuffer readBuffer = pgl.mapBuffer(PGL.PIXEL_PACK_BUFFER, + PGL.READ_ONLY); + if (readBuffer != null) { + int format = primaryGraphics ? OPENGL_NATIVE_OPAQUE : OPENGL_NATIVE; + PImage target = asyncImageSaver.getAvailableTarget(widths[tail], + heights[tail], + format); + if (target == null) return; + readBuffer.rewind(); + readBuffer.asIntBuffer().get(target.pixels); + pgl.unmapBuffer(PGL.PIXEL_PACK_BUFFER); + asyncImageSaver.saveTargetAsync(PGraphicsOpenGL.this, target, + filenames[tail]); + } + + pgl.bindBuffer(PGL.PIXEL_PACK_BUFFER, 0); + + size--; + tail = (tail + 1) % BUFFER_COUNT; + } + } @@ -5643,7 +6447,9 @@ public void loadTexture() { flush(); // To make sure the color buffer is updated. - if (primarySurface) { + if (primaryGraphics) { + updatePixelSize(); + if (pgl.isFBOBacked()) { // In the case of MSAA, this is needed so the back buffer is in sync // with the rendering. @@ -5654,26 +6460,32 @@ public void loadTexture() { // Here we go the slow route: we first copy the contents of the color // buffer into a pixels array (but we keep it in native format) and // then copy this array into the texture. - if (nativePixels == null || nativePixels.length < width * height) { - nativePixels = new int[width * height]; + if (nativePixels == null || nativePixels.length < pixelWidth * pixelHeight) { + nativePixels = new int[pixelWidth * pixelHeight]; nativePixelBuffer = PGL.allocateIntBuffer(nativePixels); } beginPixelsOp(OP_READ); try { // See comments in readPixels() for the reason for this try/catch. - pgl.readPixelsImpl(0, 0, width, height, PGL.RGBA, PGL.UNSIGNED_BYTE, + pgl.readPixelsImpl(0, 0, pixelWidth, pixelHeight, PGL.RGBA, PGL.UNSIGNED_BYTE, nativePixelBuffer); } catch (IndexOutOfBoundsException e) { } endPixelsOp(); - texture.setNative(nativePixelBuffer, 0, 0, width, height); + if (texture != null) { + texture.setNative(nativePixelBuffer, 0, 0, pixelWidth, pixelHeight); + } } } else if (offscreenMultisample) { // We need to copy the contents of the multisampled buffer to the color // buffer, so the later is up-to-date with the last drawing. - multisampleFramebuffer.copyColor(offscreenFramebuffer); + FrameBuffer ofb = offscreenFramebuffer; + FrameBuffer mfb = multisampleFramebuffer; + if (ofb != null && mfb != null) { + mfb.copyColor(ofb); + } } if (needEndDraw) { @@ -5684,14 +6496,18 @@ public void loadTexture() { // Just marks the whole texture as updated public void updateTexture() { - texture.updateTexels(); + if (texture != null) { + texture.updateTexels(); + } } // Marks the specified rectanglular subregion in the texture as // updated. public void updateTexture(int x, int y, int w, int h) { - texture.updateTexels(x, y, w, h); + if (texture != null) { + texture.updateTexels(x, y, w, h); + } } @@ -5705,12 +6521,13 @@ public void updateDisplay() { protected void loadTextureImpl(int sampling, boolean mipmap) { - if (width == 0 || height == 0) return; + updatePixelSize(); + if (pixelWidth == 0 || pixelHeight == 0) return; if (texture == null || texture.contextIsOutdated()) { Texture.Parameters params = new Texture.Parameters(ARGB, sampling, mipmap); - texture = new Texture(this, width, height, params); - texture.invertedY(true); + texture = new Texture(this, pixelWidth, pixelHeight, params); + texture.invertedY(!cameraUp); texture.colorBuffer(true); setCache(this, texture); } @@ -5718,42 +6535,51 @@ protected void loadTextureImpl(int sampling, boolean mipmap) { protected void createPTexture() { - ptexture = new Texture(this, width, height, texture.getParameters()); - ptexture.invertedY(true); - ptexture.colorBuffer(true); + updatePixelSize(); + if (texture != null) { + ptexture = new Texture(this, pixelWidth, pixelHeight, texture.getParameters()); + ptexture.invertedY(!cameraUp); + ptexture.colorBuffer(true); + } } protected void swapOffscreenTextures() { - if (ptexture != null) { + FrameBuffer ofb = offscreenFramebuffer; + if (texture != null && ptexture != null && ofb != null) { int temp = texture.glName; texture.glName = ptexture.glName; ptexture.glName = temp; - offscreenFramebuffer.setColorBuffer(texture); + ofb.setColorBuffer(texture); } } protected void drawTexture() { - // No blend so the texure replaces wherever is on the screen, - // irrespective of the alpha - pgl.disable(PGL.BLEND); - pgl.drawTexture(texture.glTarget, texture.glName, - texture.glWidth, texture.glHeight, - 0, 0, width, height); - pgl.enable(PGL.BLEND); + if (texture != null) { + // No blend so the texure replaces wherever is on the screen, + // irrespective of the alpha + pgl.disable(PGL.BLEND); + pgl.drawTexture(texture.glTarget, texture.glName, + texture.glWidth, texture.glHeight, + 0, 0, width, height); + pgl.enable(PGL.BLEND); + } } protected void drawTexture(int x, int y, int w, int h) { - // Processing Y axis is inverted with respect to OpenGL, so we need to - // invert the y coordinates of the screen rectangle. - pgl.disable(PGL.BLEND); - pgl.drawTexture(texture.glTarget, texture.glName, - texture.glWidth, texture.glHeight, width, height, - x, y, x + w, y + h, - x, height - (y + h), x + w, height - y); - pgl.enable(PGL.BLEND); + if (texture != null) { + // Processing Y axis is inverted with respect to OpenGL, so we need to + // invert the y coordinates of the screen rectangle. + pgl.disable(PGL.BLEND); + pgl.drawTexture(texture.glTarget, texture.glName, + texture.glWidth, texture.glHeight, + 0, 0, width, height, + x, y, x + w, y + h, + x, height - (y + h), x + w, height - y); + pgl.enable(PGL.BLEND); + } } @@ -5785,7 +6611,8 @@ protected void drawPTexture() { @Override public void mask(PImage alpha) { - if (alpha.width != width || alpha.height != height) { + updatePixelSize(); + if (alpha.width != pixelWidth || alpha.height != pixelHeight) { throw new RuntimeException("The PImage used with mask() must be " + "the same size as the applet."); } @@ -5840,8 +6667,9 @@ public void filter(PShader shader) { } boolean needEndDraw = false; - if (primarySurface) pgl.requestFBOLayer(); - else if (!drawing) { + if (primaryGraphics) { + pgl.enableFBOLayer(); + } else if (!drawing) { beginDraw(); needEndDraw = true; } @@ -5850,7 +6678,7 @@ else if (!drawing) { if (filterTexture == null || filterTexture.contextIsOutdated()) { filterTexture = new Texture(this, texture.width, texture.height, texture.getParameters()); - filterTexture.invertedY(true); + filterTexture.invertedY(!cameraUp); filterImage = wrapTexture(filterTexture); } filterTexture.set(texture); @@ -5916,12 +6744,11 @@ else if (!drawing) { @Override public void copy(int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) { - if (primarySurface) pgl.requestFBOLayer(); + if (primaryGraphics) pgl.enableFBOLayer(); loadTexture(); if (filterTexture == null || filterTexture.contextIsOutdated()) { - filterTexture = new Texture(this, texture.width, texture.height, - texture.getParameters()); - filterTexture.invertedY(true); + filterTexture = new Texture(this, texture.width, texture.height, texture.getParameters()); + filterTexture.invertedY(!cameraUp); filterImage = wrapTexture(filterTexture); } filterTexture.put(texture, sx, height - (sy + sh), sw, height - sy); @@ -5947,31 +6774,31 @@ public void copy(PImage src, int scrX0, scrX1; int scrY0, scrY1; if (invX) { - scrX0 = dx + dw; - scrX1 = dx; + scrX0 = (dx + dw) / src.pixelDensity; + scrX1 = dx / src.pixelDensity; } else { - scrX0 = dx; - scrX1 = dx + dw; + scrX0 = dx / src.pixelDensity; + scrX1 = (dx + dw) / src.pixelDensity; } int texX0 = sx; int texX1 = sx + sw; int texY0, texY1; if (invY) { - scrY0 = height - (dy + dh); - scrY1 = height - dy; + scrY0 = height - (dy + dh) / src.pixelDensity; + scrY1 = height - dy / src.pixelDensity; texY0 = tex.height - (sy + sh); texY1 = tex.height - sy; } else { // Because drawTexture uses bottom-to-top orientation of Y axis. - scrY0 = height - dy; - scrY1 = height - (dy + dh); + scrY0 = height - dy / src.pixelDensity; + scrY1 = height - (dy + dh) / src.pixelDensity; texY0 = sy; texY1 = sy + sh; } - pgl.drawTexture(tex.glTarget, tex.glName, - tex.glWidth, tex.glHeight, width, height, + pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, + 0, 0, width, height, texX0, texY0, texX1, texY1, scrX0, scrY0, scrX1, scrY1); @@ -6145,7 +6972,7 @@ public Texture getTexture(PImage img) { Texture tex = (Texture)initCache(img); if (tex == null) return null; - if (img.isModified() || img.isLoaded()) { + if (img.isModified()) { if (img.width != tex.width || img.height != tex.height) { tex.init(img.width, img.height); } @@ -6189,9 +7016,16 @@ protected Object initCache(PImage img) { if (tex == null || tex.contextIsOutdated()) { tex = addTexture(img); if (tex != null) { + boolean dispose = img.pixels == null; img.loadPixels(); tex.set(img.pixels, img.format); - img.setLoaded(false); + img.setModified(); + if (dispose) { + // We only used the pixels to load the image into the texture and the user did not request + // to load the pixels, so we should dispose the pixels array to avoid wasting memory + img.pixels = null; + img.loaded = false; + } } } return tex; @@ -6199,17 +7033,19 @@ protected Object initCache(PImage img) { protected void bindFrontTexture() { - if (primarySurface) { + if (primaryGraphics) { pgl.bindFrontTexture(); } else { - if (ptexture == null) createPTexture(); + if (ptexture == null) { + createPTexture(); + } ptexture.bind(); } } protected void unbindFrontTexture() { - if (primarySurface) { + if (primaryGraphics) { pgl.unbindFrontTexture(); } else { ptexture.unbind(); @@ -6238,7 +7074,8 @@ protected Texture addTexture(PImage img, Texture.Parameters params) { if (img.parent == null) { img.parent = parent; } - Texture tex = new Texture(this, img.width, img.height, params); + Texture tex = new Texture(this, img.pixelWidth, img.pixelHeight, params); + tex.invertedY(cameraUp); // Pixels are read upside down if camera us pointing up setCache(img, tex); return tex; } @@ -6246,7 +7083,8 @@ protected Texture addTexture(PImage img, Texture.Parameters params) { protected void checkTexture(Texture tex) { if (!tex.colorBuffer() && - tex.usingMipmaps == hints[DISABLE_TEXTURE_MIPMAPS]) { + (tex.usingMipmaps == hints[DISABLE_TEXTURE_MIPMAPS] || + tex.currentSampling() != textureSampling)) { if (hints[DISABLE_TEXTURE_MIPMAPS]) { tex.usingMipmaps(false, textureSampling); } else { @@ -6286,12 +7124,9 @@ protected void updateTexture(PImage img, Texture tex) { int w = img.getModifiedX2() - x; int h = img.getModifiedY2() - y; tex.set(img.pixels, x, y, w, h, img.format); - } else if (img.isLoaded()) { - tex.set(img.pixels, 0, 0, img.width, img.height, img.format); } } img.setModified(false); - img.setLoaded(false); } @@ -6311,12 +7146,13 @@ protected void deleteSurfaceTextures() { protected boolean checkGLThread() { - if (pgl.threadIsCurrent()) { - return true; - } else { - PGraphics.showWarning(OPENGL_THREAD_ERROR); - return false; - } +// if (pgl.threadIsCurrent()) { +// return true; +// } else { +// PGraphics.showWarning(OPENGL_THREAD_ERROR); +// return false; +// } + return true; } @@ -6337,24 +7173,28 @@ public void resize(int wide, int high) { protected void initPrimary() { - pgl.initSurface(quality); + if (initialized) return; + pgl.initSurface(smooth); if (texture != null) { removeCache(this); - texture = ptexture = null; + texture = null; + ptexture = null; } initialized = true; } protected void beginOnscreenDraw() { - pgl.beginDraw(clearColorBuffer); + updatePixelSize(); + restoreSurface(); + pgl.beginRender(); if (drawFramebuffer == null) { - drawFramebuffer = new FrameBuffer(this, width, height, true); + drawFramebuffer = new FrameBuffer(this, pixelWidth, pixelHeight, true); } drawFramebuffer.setFBO(pgl.getDrawFramebuffer()); if (readFramebuffer == null) { - readFramebuffer = new FrameBuffer(this, width, height, true); + readFramebuffer = new FrameBuffer(this, pixelWidth, pixelHeight, true); } readFramebuffer.setFBO(pgl.getReadFramebuffer()); if (currentFramebuffer == null) { @@ -6369,7 +7209,7 @@ protected void beginOnscreenDraw() { protected void endOnscreenDraw() { - pgl.endDraw(clearColorBuffer0); + pgl.endRender(parent.sketchWindowColor()); } @@ -6377,43 +7217,49 @@ protected void initOffscreen() { // Getting the context and capabilities from the main renderer. loadTextureImpl(textureSampling, false); - // In case of reinitialization (for example, when the smooth level + FrameBuffer ofb = offscreenFramebuffer; + FrameBuffer mfb = multisampleFramebuffer; + + // In case of re-initialization (for example, when the smooth level // is changed), we make sure that all the OpenGL resources associated // to the surface are released by calling delete(). - if (offscreenFramebuffer != null) { - offscreenFramebuffer.dispose(); + if (ofb != null) { + ofb.dispose(); + ofb = null; } - if (multisampleFramebuffer != null) { - multisampleFramebuffer.dispose(); + if (mfb != null) { + mfb.dispose(); + mfb = null; } boolean packed = depthBits == 24 && stencilBits == 8 && packedDepthStencilSupported; - if (PGraphicsOpenGL.fboMultisampleSupported && 1 < quality) { - multisampleFramebuffer = - new FrameBuffer(this, texture.glWidth, texture.glHeight, quality, 0, - depthBits, stencilBits, packed, false); - - multisampleFramebuffer.clear(); + if (PGraphicsOpenGL.fboMultisampleSupported && 1 < PGL.smoothToSamples(smooth)) { + mfb = new FrameBuffer(this, texture.glWidth, texture.glHeight, PGL.smoothToSamples(smooth), 0, + depthBits, stencilBits, packed, false); + mfb.clear(); + multisampleFramebuffer = mfb; offscreenMultisample = true; // The offscreen framebuffer where the multisampled image is finally drawn - // to doesn't need depth and stencil buffers since they are part of the - // multisampled framebuffer. - offscreenFramebuffer = - new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, 0, 0, - false, false); - + // to. If depth reading is disabled it doesn't need depth and stencil buffers + // since they are part of the multisampled framebuffer. + if (hints[ENABLE_BUFFER_READING]) { + ofb = new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, + depthBits, stencilBits, packed, false); + } else { + ofb = new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, + 0, 0, false, false); + } } else { - quality = 0; - offscreenFramebuffer = - new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, - depthBits, stencilBits, packed, false); + smooth = 0; + ofb = new FrameBuffer(this, texture.glWidth, texture.glHeight, 1, 1, + depthBits, stencilBits, packed, false); offscreenMultisample = false; } - - offscreenFramebuffer.setColorBuffer(texture); - offscreenFramebuffer.clear(); + ofb.setColorBuffer(texture); + ofb.clear(); + offscreenFramebuffer = ofb; initialized = true; } @@ -6423,10 +7269,10 @@ protected void beginOffscreenDraw() { if (!initialized) { initOffscreen(); } else { - boolean outdated = offscreenFramebuffer != null && - offscreenFramebuffer.contextIsOutdated(); - boolean outdatedMulti = multisampleFramebuffer != null && - multisampleFramebuffer.contextIsOutdated(); + FrameBuffer ofb = offscreenFramebuffer; + FrameBuffer mfb = multisampleFramebuffer; + boolean outdated = ofb != null && ofb.contextIsOutdated(); + boolean outdatedMulti = mfb != null && mfb.contextIsOutdated(); if (outdated || outdatedMulti) { restartPGL(); initOffscreen(); @@ -6440,9 +7286,15 @@ protected void beginOffscreenDraw() { pushFramebuffer(); if (offscreenMultisample) { - setFramebuffer(multisampleFramebuffer); + FrameBuffer mfb = multisampleFramebuffer; + if (mfb != null) { + setFramebuffer(mfb); + } } else { - setFramebuffer(offscreenFramebuffer); + FrameBuffer ofb = offscreenFramebuffer; + if (ofb != null) { + setFramebuffer(ofb); + } } // Render previous back texture (now is the front) as background @@ -6460,7 +7312,11 @@ protected void beginOffscreenDraw() { protected void endOffscreenDraw() { if (offscreenMultisample) { - multisampleFramebuffer.copyColor(offscreenFramebuffer); + FrameBuffer ofb = offscreenFramebuffer; + FrameBuffer mfb = multisampleFramebuffer; + if (ofb != null && mfb != null) { + mfb.copyColor(ofb); + } } popFramebuffer(); @@ -6475,7 +7331,9 @@ protected void endOffscreenDraw() { pgl.colorMask(true, true, true, true); } - texture.updateTexels(); // Mark all texels in screen texture as modified. + if (texture != null) { + texture.updateTexels(); // Mark all texels in screen texture as modified. + } getPrimaryPG().restoreGL(); } @@ -6489,7 +7347,14 @@ protected void setViewport() { } - protected void setDrawDefaults() { + @Override + protected void checkSettings() { + super.checkSettings(); + setGLSettings(); + } + + + protected void setGLSettings() { inGeo.clear(); tessGeo.clear(); texCache.clear(); @@ -6515,26 +7380,33 @@ protected void setDrawDefaults() { flushMode = FLUSH_WHEN_FULL; } - if (primarySurface) { - pgl.getIntegerv(PGL.SAMPLES, intBuffer); - int temp = intBuffer.get(0); - if (quality != temp && 1 < temp && 1 < quality) { - quality = temp; - } + if (primaryGraphics) { +// pgl.getIntegerv(PGL.SAMPLES, intBuffer); +// int temp = intBuffer.get(0); +// if (smooth != temp && 1 < temp && 1 < smooth) { + // TODO check why the samples is higher that initialized smooth level. +// quality = temp; +// } } - if (quality < 2) { + if (smooth < 1) { pgl.disable(PGL.MULTISAMPLE); } else { - pgl.enable(PGL.MULTISAMPLE); + // work around runtime exceptions in Broadcom's VC IV driver + if (false == OPENGL_RENDERER.equals("VideoCore IV HW")) { + pgl.enable(PGL.MULTISAMPLE); + } + } + // work around runtime exceptions in Broadcom's VC IV driver + if (false == OPENGL_RENDERER.equals("VideoCore IV HW")) { + pgl.disable(PGL.POLYGON_SMOOTH); } - pgl.disable(PGL.POLYGON_SMOOTH); - if (sized) { + if (sized || parent.frameCount == 0) { // reapplySettings(); // To avoid having garbage in the screen after a resize, // in the case background is not called in draw(). - if (primarySurface) { + if (primaryGraphics) { background(backgroundColor); } else { // offscreen surfaces are transparent by default. @@ -6564,13 +7436,11 @@ protected void setDrawDefaults() { lightSpecular(0, 0, 0); } - // Vertices should be specified by user in CW order (left-handed) - // That is CCW order (right-handed). Vertex shader inverts - // Y-axis and outputs vertices in CW order (right-handed). - // Culling occurs after the vertex shader, so FRONT FACE - // has to be set to CW (right-handed) for OpenGL to correctly - // recognize FRONT and BACK faces. - pgl.frontFace(PGL.CW); + // The GL coordinate system is right-handed, so that facing + // polygons are CCW in window coordinates, whereas are CW + // in the left-handed system that is Processing's default (with + // its Y axis pointing down) + pgl.frontFace(cameraUp ? PGL.CCW : PGL.CW); pgl.disable(PGL.CULL_FACE); // Processing uses only one texture unit. @@ -6580,20 +7450,7 @@ protected void setDrawDefaults() { normalX = normalY = 0; normalZ = 1; - // Clear depth and stencil buffers. - pgl.depthMask(true); - pgl.clearDepth(1); - pgl.clearStencil(0); - pgl.clear(PGL.DEPTH_BUFFER_BIT | PGL.STENCIL_BUFFER_BIT); - - if (!settingsInited) { - defaultSettings(); - } - - if (restoreSurface) { - restoreSurfaceFromPixels(); - restoreSurface = false; - } + pgl.clearDepthStencil(); if (hints[DISABLE_DEPTH_MASK]) { pgl.depthMask(false); @@ -6603,11 +7460,8 @@ protected void setDrawDefaults() { pixelsOp = OP_NONE; - clearColorBuffer0 = clearColorBuffer; - clearColorBuffer = false; - modified = false; - arePixelsUpToDate = false; + loaded = false; } @@ -6623,6 +7477,8 @@ protected void getGLParameters() { fboMultisampleSupported = pgl.hasFboMultisampleSupport(); packedDepthStencilSupported = pgl.hasPackedDepthStencilSupport(); anisoSamplingSupported = pgl.hasAnisoSamplingSupport(); + readBufferSupported = pgl.hasReadBuffer(); + drawBufferSupported = pgl.hasDrawBuffer(); try { pgl.blendEquation(PGL.FUNC_ADD); @@ -6637,14 +7493,27 @@ protected void getGLParameters() { pgl.getIntegerv(PGL.MAX_TEXTURE_SIZE, intBuffer); maxTextureSize = intBuffer.get(0); - pgl.getIntegerv(PGL.MAX_SAMPLES, intBuffer); - maxSamples = intBuffer.get(0); + // work around runtime exceptions in Broadcom's VC IV driver + if (false == OPENGL_RENDERER.equals("VideoCore IV HW")) { + pgl.getIntegerv(PGL.MAX_SAMPLES, intBuffer); + maxSamples = intBuffer.get(0); + } if (anisoSamplingSupported) { pgl.getFloatv(PGL.MAX_TEXTURE_MAX_ANISOTROPY, floatBuffer); maxAnisoAmount = floatBuffer.get(0); } + // overwrite the default shaders with vendor specific versions + // if needed + if (OPENGL_RENDERER.equals("VideoCore IV HW") || // Broadcom's binary driver for Raspberry Pi + OPENGL_RENDERER.equals("Gallium 0.4 on VC4")) { // Mesa driver for same hardware + defLightShaderVertURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/LightVert-vc4.glsl"); + defTexlightShaderVertURL = + PGraphicsOpenGL.class.getResource("/assets/shaders/TexLightVert-vc4.glsl"); + } + glParamsRead = true; } @@ -6667,25 +7536,25 @@ public PShader loadShader(String fragFilename) { shader.setType(type); shader.setFragmentShader(fragFilename); if (type == PShader.POINT) { - String[] vertSource = pgl.loadVertexShader(defPointShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defPointShaderVertURL); shader.setVertexShader(vertSource); } else if (type == PShader.LINE) { - String[] vertSource = pgl.loadVertexShader(defLineShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defLineShaderVertURL); shader.setVertexShader(vertSource); } else if (type == PShader.TEXLIGHT) { - String[] vertSource = pgl.loadVertexShader(defTexlightShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defTexlightShaderVertURL); shader.setVertexShader(vertSource); } else if (type == PShader.LIGHT) { - String[] vertSource = pgl.loadVertexShader(defLightShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defLightShaderVertURL); shader.setVertexShader(vertSource); } else if (type == PShader.TEXTURE) { - String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL); shader.setVertexShader(vertSource); } else if (type == PShader.COLOR) { - String[] vertSource = pgl.loadVertexShader(defColorShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defColorShaderVertURL); shader.setVertexShader(vertSource); } else { - String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL, 120); + String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL); shader.setVertexShader(vertSource); } return shader; @@ -6694,15 +7563,15 @@ public PShader loadShader(String fragFilename) { @Override public PShader loadShader(String fragFilename, String vertFilename) { + PShader shader = null; if (fragFilename == null || fragFilename.equals("")) { PGraphics.showWarning(MISSING_FRAGMENT_SHADER); - return null; - } else if (fragFilename == null || fragFilename.equals("")) { + } else if (vertFilename == null || vertFilename.equals("")) { PGraphics.showWarning(MISSING_VERTEX_SHADER); - return null; } else { - return new PShader(parent, vertFilename, fragFilename); + shader = new PShader(parent, vertFilename, fragFilename); } + return shader; } @@ -6710,6 +7579,7 @@ public PShader loadShader(String fragFilename, String vertFilename) { public void shader(PShader shader) { flush(); // Flushing geometry drawn with a different shader. + if (shader != null) shader.init(); if (shader.isPolyShader()) polyShader = shader; else if (shader.isLineShader()) lineShader = shader; else if (shader.isPointShader()) pointShader = shader; @@ -6721,6 +7591,7 @@ public void shader(PShader shader) { public void shader(PShader shader, int kind) { flush(); // Flushing geometry drawn with a different shader. + if (shader != null) shader.init(); if (kind == TRIANGLES) polyShader = shader; else if (kind == LINES) lineShader = shader; else if (kind == POINTS) pointShader = shader; @@ -6750,47 +7621,32 @@ public void resetShader(int kind) { } - protected void deleteDefaultShaders() { - // The default shaders contains references to the PGraphics object that - // creates them, so when restarting the renderer, those references should - // dissapear. - defColorShader = null; - defTextureShader = null; - defLightShader = null; - defTexlightShader = null; - defLineShader = null; - defPointShader = null; - maskShader = null; - } - - protected PShader getPolyShader(boolean lit, boolean tex) { PShader shader; PGraphicsOpenGL ppg = getPrimaryPG(); boolean useDefault = polyShader == null; if (polyShader != null) { - polyShader.setRenderer(this); - polyShader.loadAttributes(); - polyShader.loadUniforms(); + updateShader(polyShader); +// polyShader.setRenderer(this); +// polyShader.loadAttributes(); +// polyShader.loadUniforms(); } if (lit) { if (tex) { - if (useDefault || !polyShader.checkPolyType(PShader.TEXLIGHT)) { + if (useDefault || !isPolyShaderTexLight(polyShader)) { if (ppg.defTexlightShader == null) { - String[] vertSource = pgl.loadVertexShader(defTexlightShaderVertURL, 120); - String[] fragSource = pgl.loadFragmentShader(defTexlightShaderFragURL, 120); - ppg.defTexlightShader = new PShader(parent, vertSource, fragSource); + ppg.defTexlightShader = loadShaderFromURL(defTexlightShaderFragURL, + defTexlightShaderVertURL); } shader = ppg.defTexlightShader; } else { shader = polyShader; } } else { - if (useDefault || !polyShader.checkPolyType(PShader.LIGHT)) { + if (useDefault || !isPolyShaderLight(polyShader)) { if (ppg.defLightShader == null) { - String[] vertSource = pgl.loadVertexShader(defLightShaderVertURL, 120); - String[] fragSource = pgl.loadFragmentShader(defLightShaderFragURL, 120); - ppg.defLightShader = new PShader(parent, vertSource, fragSource); + ppg.defLightShader = loadShaderFromURL(defLightShaderFragURL, + defLightShaderVertURL); } shader = ppg.defLightShader; } else { @@ -6798,41 +7654,77 @@ protected PShader getPolyShader(boolean lit, boolean tex) { } } } else { - if (polyShader != null && polyShader.accessLightAttribs()) { + if (isPolyShaderUsingLights(polyShader)) { PGraphics.showWarning(SHADER_NEED_LIGHT_ATTRIBS); useDefault = true; } - if (tex) { - if (useDefault || !polyShader.checkPolyType(PShader.TEXTURE)) { - if (ppg.defTextureShader == null) { - String[] vertSource = pgl.loadVertexShader(defTextureShaderVertURL, 120); - String[] fragSource = pgl.loadFragmentShader(defTextureShaderFragURL, 120); - ppg.defTextureShader = new PShader(parent, vertSource, fragSource); - } - shader = ppg.defTextureShader; - } else { - shader = polyShader; - } - } else { - if (useDefault || !polyShader.checkPolyType(PShader.COLOR)) { - if (ppg.defColorShader == null) { - String[] vertSource = pgl.loadVertexShader(defColorShaderVertURL, 120); - String[] fragSource = pgl.loadFragmentShader(defColorShaderFragURL, 120); - ppg.defColorShader = new PShader(parent, vertSource, fragSource); - } - shader = ppg.defColorShader; - } else { - shader = polyShader; - } - } - } - if (shader != polyShader) { - shader.setRenderer(this); - shader.loadAttributes(); - shader.loadUniforms(); - } - return shader; + if (tex) { + if (useDefault || !isPolyShaderTex(polyShader)) { + if (ppg.defTextureShader == null) { + ppg.defTextureShader = loadShaderFromURL(defTextureShaderFragURL, + defTextureShaderVertURL); + } + shader = ppg.defTextureShader; + } else { + shader = polyShader; + } + } else { + if (useDefault || !isPolyShaderColor(polyShader)) { + if (ppg.defColorShader == null) { + ppg.defColorShader = loadShaderFromURL(defColorShaderFragURL, + defColorShaderVertURL); + } + shader = ppg.defColorShader; + } else { + shader = polyShader; + } + } + } + if (shader != polyShader) { + updateShader(shader); + } + updateShader(shader); + return shader; + } + + + protected void updateShader(PShader shader) { + shader.setRenderer(this); + shader.loadAttributes(); + shader.loadUniforms(); + } + + + protected PShader loadShaderFromURL(URL fragURL, URL vertURL) { + String[] vertSource = pgl.loadVertexShader(vertURL); + String[] fragSource = pgl.loadFragmentShader(fragURL); + return new PShader(parent, vertSource, fragSource); + } + + + protected boolean isPolyShaderTexLight(PShader shader) { + return shader.checkPolyType(PShader.TEXLIGHT); + } + + + protected boolean isPolyShaderLight(PShader shader) { + return shader.checkPolyType(PShader.LIGHT); + } + + + protected boolean isPolyShaderTex(PShader shader) { + return shader.checkPolyType(PShader.TEXTURE); + } + + + protected boolean isPolyShaderColor(PShader shader) { + return shader.checkPolyType(PShader.COLOR); + } + + + protected boolean isPolyShaderUsingLights(PShader shader) { + return shader != null && shader.accessLightAttribs(); } @@ -6841,8 +7733,8 @@ protected PShader getLineShader() { PGraphicsOpenGL ppg = getPrimaryPG(); if (lineShader == null) { if (ppg.defLineShader == null) { - String[] vertSource = pgl.loadVertexShader(defLineShaderVertURL, 120); - String[] fragSource = pgl.loadFragmentShader(defLineShaderFragURL, 120); + String[] vertSource = pgl.loadVertexShader(defLineShaderVertURL); + String[] fragSource = pgl.loadFragmentShader(defLineShaderFragURL); ppg.defLineShader = new PShader(parent, vertSource, fragSource); } shader = ppg.defLineShader; @@ -6861,8 +7753,8 @@ protected PShader getPointShader() { PGraphicsOpenGL ppg = getPrimaryPG(); if (pointShader == null) { if (ppg.defPointShader == null) { - String[] vertSource = pgl.loadVertexShader(defPointShaderVertURL, 120); - String[] fragSource = pgl.loadFragmentShader(defPointShaderFragURL, 120); + String[] vertSource = pgl.loadVertexShader(defPointShaderVertURL); + String[] fragSource = pgl.loadFragmentShader(defPointShaderFragURL); ppg.defPointShader = new PShader(parent, vertSource, fragSource); } shader = ppg.defPointShader; @@ -6888,18 +7780,221 @@ static protected int expandArraySize(int currSize, int newMinSize) { return newSize; } + ////////////////////////////////////////////////////////////// + + // Generic vertex attributes. + + + static protected AttributeMap newAttributeMap() { + return new AttributeMap(); + } + + + static protected class AttributeMap extends HashMap { + public ArrayList names = new ArrayList(); + public int numComp = 0; // number of components for a single vertex + + @Override + public VertexAttribute put(String key, VertexAttribute value) { + VertexAttribute prev = super.put(key, value); + names.add(key); + if (value.kind == VertexAttribute.COLOR) numComp += 4; + else numComp += value.size; + return prev; + } + + public VertexAttribute get(int i) { + return super.get(names.get(i)); + } + } + + + static protected class VertexAttribute { + static final int POSITION = 0; + static final int NORMAL = 1; + static final int COLOR = 2; + static final int OTHER = 3; + + PGraphicsOpenGL pg; + String name; + int kind; // POSITION, NORMAL, COLOR, OTHER + int type; // GL_INT, GL_FLOAT, GL_BOOL + int size; // number of elements (1, 2, 3, or 4) + int tessSize; + int elementSize; + VertexBuffer buf; + int glLoc; + + float[] fvalues; + int[] ivalues; + byte[] bvalues; + + // For use in PShape + boolean modified; + int firstModified; + int lastModified; + boolean active; + + VertexAttribute(PGraphicsOpenGL pg, String name, int kind, int type, int size) { + this.pg = pg; + this.name = name; + this.kind = kind; + this.type = type; + this.size = size; + + if (kind == POSITION) { + tessSize = 4; // for w + } else { + tessSize = size; + } + + if (type == PGL.FLOAT) { + elementSize = PGL.SIZEOF_FLOAT; + fvalues = new float[size]; + } else if (type == PGL.INT) { + elementSize = PGL.SIZEOF_INT; + ivalues = new int[size]; + } else if (type == PGL.BOOL) { + elementSize = PGL.SIZEOF_INT; + bvalues = new byte[size]; + } + + buf = null; + glLoc = -1; + + modified = false; + firstModified = PConstants.MAX_INT; + lastModified = PConstants.MIN_INT; + + active = true; + } + + public boolean diff(VertexAttribute attr) { + return !name.equals(attr.name) || + kind != attr.kind || + type != attr.type || + size != attr.size || + tessSize != attr.tessSize || + elementSize != attr.elementSize; + } + + boolean isPosition() { + return kind == POSITION; + } + + boolean isNormal() { + return kind == NORMAL; + } + + boolean isColor() { + return kind == COLOR; + } + + boolean isOther() { + return kind == OTHER; + } + + boolean isFloat() { + return type == PGL.FLOAT; + } + + boolean isInt() { + return type == PGL.INT; + } + + boolean isBool() { + return type == PGL.BOOL; + } + + boolean bufferCreated() { + return buf != null && 0 < buf.glId; + } + + void createBuffer(PGL pgl) { + buf = new VertexBuffer(pg, PGL.ARRAY_BUFFER, size, elementSize, false); + } + + void deleteBuffer(PGL pgl) { + if (buf.glId != 0) { + intBuffer.put(0, buf.glId); + if (pgl.threadIsCurrent()) pgl.deleteBuffers(1, intBuffer); + } + } + + void bind(PGL pgl) { + pgl.enableVertexAttribArray(glLoc); + } + + void unbind(PGL pgl) { + pgl.disableVertexAttribArray(glLoc); + } + + boolean active(PShader shader) { + if (active) { + if (glLoc == -1) { + glLoc = shader.getAttributeLoc(name); + if (glLoc == -1) active = false; + } + } + return active; + } + + int sizeInBytes(int length) { + return length * tessSize * elementSize; + } + + void set(float x, float y, float z) { + fvalues[0] = x; + fvalues[1] = y; + fvalues[2] = z; + } + + void set(int c) { + ivalues[0] = c; + } + + void set(float[] values) { + PApplet.arrayCopy(values, 0, fvalues, 0, size); + } + + void set(int[] values) { + PApplet.arrayCopy(values, 0, ivalues, 0, size); + } + + void set(boolean[] values) { + for (int i = 0; i < values.length; i++) { + bvalues[i] = (byte)(values[i] ? 1 : 0); + } + } + + void add(float[] dstValues, int dstIdx) { + PApplet.arrayCopy(fvalues, 0, dstValues, dstIdx, size); + } + + void add(int[] dstValues, int dstIdx) { + PApplet.arrayCopy(ivalues, 0, dstValues, dstIdx, size); + } + + void add(byte[] dstValues, int dstIdx) { + PApplet.arrayCopy(bvalues, 0, dstValues, dstIdx, size); + } + } + + ////////////////////////////////////////////////////////////// // Input (raw) and Tessellated geometry, tessellator. - static protected InGeometry newInGeometry(PGraphicsOpenGL pg, int mode) { - return new InGeometry(pg, mode); + static protected InGeometry newInGeometry(PGraphicsOpenGL pg, AttributeMap attr, + int mode) { + return new InGeometry(pg, attr, mode); } - static protected TessGeometry newTessGeometry(PGraphicsOpenGL pg, int mode) { - return new TessGeometry(pg, mode); + static protected TessGeometry newTessGeometry(PGraphicsOpenGL pg, + AttributeMap attr, int mode) { + return new TessGeometry(pg, attr, mode); } @@ -7036,17 +8131,19 @@ static protected class IndexCache { int[] indexOffset; int[] vertexCount; int[] vertexOffset; + int[] counter; IndexCache() { allocate(); } void allocate() { + size = 0; indexCount = new int[2]; indexOffset = new int[2]; vertexCount = new int[2]; vertexOffset = new int[2]; - size = 0; + counter = null; } void clear() { @@ -7079,9 +8176,17 @@ int getLast() { return size - 1; } + void setCounter(int[] counter) { + this.counter = counter; + } + void incCounts(int index, int icount, int vcount) { indexCount[index] += icount; vertexCount[index] += vcount; + if (counter != null) { + counter[0] += icount; + counter[1] += vcount; + } } void init(int n) { @@ -7138,6 +8243,7 @@ void expandVertexOffset(int n) { static protected class InGeometry { PGraphicsOpenGL pg; int renderMode; + AttributeMap attribs; int vertexCount; int codeCount; @@ -7162,6 +8268,11 @@ static protected class InGeometry { int[] emissive; float[] shininess; + // Generic attributes + HashMap fattribs; + HashMap iattribs; + HashMap battribs; + // Internally used by the addVertex() methods. int fillColor; int strokeColor; @@ -7172,8 +8283,9 @@ static protected class InGeometry { float shininessFactor; float normalX, normalY, normalZ; - InGeometry(PGraphicsOpenGL pg, int mode) { + InGeometry(PGraphicsOpenGL pg, AttributeMap attr, int mode) { this.pg = pg; + this.attribs = attr; renderMode = mode; allocate(); } @@ -7205,9 +8317,26 @@ void allocate() { shininess = new float[PGL.DEFAULT_IN_VERTICES]; edges = new int[PGL.DEFAULT_IN_EDGES][3]; + fattribs = new HashMap(); + iattribs = new HashMap(); + battribs = new HashMap(); + clear(); } + void initAttrib(VertexAttribute attrib) { + if (attrib.type == PGL.FLOAT) { + float[] temp = new float[attrib.size * PGL.DEFAULT_IN_VERTICES]; + fattribs.put(attrib.name, temp); + } else if (attrib.type == PGL.INT) { + int[] temp = new int[attrib.size * PGL.DEFAULT_IN_VERTICES]; + iattribs.put(attrib.name, temp); + } else if (attrib.type == PGL.BOOL) { + byte[] temp = new byte[attrib.size * PGL.DEFAULT_IN_VERTICES]; + battribs.put(attrib.name, temp); + } + } + void vertexCheck() { if (vertexCount == vertices.length / 3) { int newSize = vertexCount << 1; @@ -7222,6 +8351,7 @@ void vertexCheck() { expandSpecular(newSize); expandEmissive(newSize); expandShininess(newSize); + expandAttribs(newSize); } } @@ -7283,8 +8413,11 @@ int getNumEdgeVertices(boolean bevel) { if (bevel) { for (int i = 0; i < edgeCount; i++) { int[] edge = edges[i]; - if (edge[2] == EDGE_MIDDLE || edge[2] == EDGE_START) bevVert++; - if (edge[2] == EDGE_CLOSE) segVert--; + if (edge[2] == EDGE_MIDDLE || edge[2] == EDGE_START) bevVert += 3; + if (edge[2] == EDGE_CLOSE) { + bevVert += 5; + segVert--; + } } } else { segVert -= getNumEdgeClosures(); @@ -7299,7 +8432,10 @@ int getNumEdgeIndices(boolean bevel) { for (int i = 0; i < edgeCount; i++) { int[] edge = edges[i]; if (edge[2] == EDGE_MIDDLE || edge[2] == EDGE_START) bevInd++; - if (edge[2] == EDGE_CLOSE) segInd--; + if (edge[2] == EDGE_CLOSE) { + bevInd++; + segInd--; + } } } else { segInd -= getNumEdgeClosures(); @@ -7338,6 +8474,42 @@ int getVertexSum(PVector v) { return vertexCount; } + double[] getAttribVector(int idx) { + double[] vector = new double[attribs.numComp]; + int vidx = 0; + for (int i = 0; i < attribs.size(); i++) { + VertexAttribute attrib = attribs.get(i); + String name = attrib.name; + int aidx = attrib.size * idx; + if (attrib.isColor()) { + int[] iarray = iattribs.get(name); + int col = iarray[aidx]; + vector[vidx++] = (col >> 24) & 0xFF; + vector[vidx++] = (col >> 16) & 0xFF; + vector[vidx++] = (col >> 8) & 0xFF; + vector[vidx++] = (col >> 0) & 0xFF; + } else { + if (attrib.isFloat()) { + float[] farray = fattribs.get(name); + for (int n = 0; n < attrib.size; n++) { + vector[vidx++] = farray[aidx++]; + } + } else if (attrib.isInt()) { + int[] iarray = iattribs.get(name); + for (int n = 0; n < attrib.size; n++) { + vector[vidx++] = iarray[aidx++]; + } + } else if (attrib.isBool()) { + byte[] barray = battribs.get(name); + for (int n = 0; n < attrib.size; n++) { + vector[vidx++] = barray[aidx++]; + } + } + } + } + return vector; + } + // ----------------------------------------------------------------- // // Expand arrays @@ -7402,6 +8574,40 @@ void expandShininess(int n) { shininess = temp; } + void expandAttribs(int n) { + for (String name: attribs.keySet()) { + VertexAttribute attrib = attribs.get(name); + if (attrib.type == PGL.FLOAT) { + expandFloatAttrib(attrib, n); + } else if (attrib.type == PGL.INT) { + expandIntAttrib(attrib, n); + } else if (attrib.type == PGL.BOOL) { + expandBoolAttrib(attrib, n); + } + } + } + + void expandFloatAttrib(VertexAttribute attrib, int n) { + float[] values = fattribs.get(attrib.name); + float temp[] = new float[attrib.size * n]; + PApplet.arrayCopy(values, 0, temp, 0, attrib.size * vertexCount); + fattribs.put(attrib.name, temp); + } + + void expandIntAttrib(VertexAttribute attrib, int n) { + int[] values = iattribs.get(attrib.name); + int temp[] = new int[attrib.size * n]; + PApplet.arrayCopy(values, 0, temp, 0, attrib.size * vertexCount); + iattribs.put(attrib.name, temp); + } + + void expandBoolAttrib(VertexAttribute attrib, int n) { + byte[] values = battribs.get(attrib.name); + byte temp[] = new byte[attrib.size * n]; + PApplet.arrayCopy(values, 0, temp, 0, attrib.size * vertexCount); + battribs.put(attrib.name, temp); + } + void expandCodes(int n) { int temp[] = new int[n]; PApplet.arrayCopy(codes, 0, temp, 0, codeCount); @@ -7430,6 +8636,7 @@ void trim() { trimSpecular(); trimEmissive(); trimShininess(); + trimAttribs(); } if (0 < codeCount && codeCount < codes.length) { @@ -7513,12 +8720,52 @@ void trimEdges() { edges = temp; } + void trimAttribs() { + for (String name: attribs.keySet()) { + VertexAttribute attrib = attribs.get(name); + if (attrib.type == PGL.FLOAT) { + trimFloatAttrib(attrib); + } else if (attrib.type == PGL.INT) { + trimIntAttrib(attrib); + } else if (attrib.type == PGL.BOOL) { + trimBoolAttrib(attrib); + } + } + } + + void trimFloatAttrib(VertexAttribute attrib) { + float[] values = fattribs.get(attrib.name); + float temp[] = new float[attrib.size * vertexCount]; + PApplet.arrayCopy(values, 0, temp, 0, attrib.size * vertexCount); + fattribs.put(attrib.name, temp); + } + + void trimIntAttrib(VertexAttribute attrib) { + int[] values = iattribs.get(attrib.name); + int temp[] = new int[attrib.size * vertexCount]; + PApplet.arrayCopy(values, 0, temp, 0, attrib.size * vertexCount); + iattribs.put(attrib.name, temp); + } + + void trimBoolAttrib(VertexAttribute attrib) { + byte[] values = battribs.get(attrib.name); + byte temp[] = new byte[attrib.size * vertexCount]; + PApplet.arrayCopy(values, 0, temp, 0, attrib.size * vertexCount); + battribs.put(attrib.name, temp); + } + // ----------------------------------------------------------------- // // Vertices int addVertex(float x, float y, boolean brk) { - return addVertex(x, y, VERTEX, brk); + return addVertex(x, y, 0, + fillColor, + normalX, normalY, normalZ, + 0, 0, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininessFactor, + VERTEX, brk); } int addVertex(float x, float y, @@ -7528,15 +8775,20 @@ int addVertex(float x, float y, normalX, normalY, normalZ, 0, 0, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, - shininessFactor, + ambientColor, specularColor, emissiveColor, shininessFactor, code, brk); } int addVertex(float x, float y, float u, float v, boolean brk) { - return addVertex(x, y, u, v, VERTEX, brk); + return addVertex(x, y, 0, + fillColor, + normalX, normalY, normalZ, + u, v, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininessFactor, + VERTEX, brk); } int addVertex(float x, float y, @@ -7547,31 +8799,40 @@ int addVertex(float x, float y, normalX, normalY, normalZ, u, v, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, - shininessFactor, + ambientColor, specularColor, emissiveColor, shininessFactor, code, brk); } int addVertex(float x, float y, float z, boolean brk) { - return addVertex(x, y, z, VERTEX, brk); + return addVertex(x, y, z, + fillColor, + normalX, normalY, normalZ, + 0, 0, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininessFactor, + VERTEX, brk); } - int addVertex(float x, float y, float z, - int code, boolean brk) { + int addVertex(float x, float y, float z, int code, boolean brk) { return addVertex(x, y, z, fillColor, normalX, normalY, normalZ, 0, 0, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, - shininessFactor, + ambientColor, specularColor, emissiveColor, shininessFactor, code, brk); } int addVertex(float x, float y, float z, float u, float v, boolean brk) { - return addVertex(x, y, z, u, v, VERTEX, brk); + return addVertex(x, y, z, + fillColor, + normalX, normalY, normalZ, + u, v, + strokeColor, strokeWeight, + ambientColor, specularColor, emissiveColor, shininessFactor, + VERTEX, brk); } int addVertex(float x, float y, float z, @@ -7582,8 +8843,7 @@ int addVertex(float x, float y, float z, normalX, normalY, normalZ, u, v, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, - shininessFactor, + ambientColor, specularColor, emissiveColor, shininessFactor, code, brk); } @@ -7621,6 +8881,21 @@ int addVertex(float x, float y, float z, emissive[vertexCount] = PGL.javaToNativeARGB(em); shininess[vertexCount] = shine; + for (String name: attribs.keySet()) { + VertexAttribute attrib = attribs.get(name); + index = attrib.size * vertexCount; + if (attrib.type == PGL.FLOAT) { + float[] values = fattribs.get(name); + attrib.add(values, index); + } else if (attrib.type == PGL.INT) { + int[] values = iattribs.get(name); + attrib.add(values, index); + } else if (attrib.type == PGL.BOOL) { + byte[] values = battribs.get(name); + attrib.add(values, index); + } + } + if (brk || (code == VERTEX && codes != null) || code == BEZIER_VERTEX || code == QUADRATIC_VERTEX || @@ -7651,14 +8926,16 @@ int addVertex(float x, float y, float z, public void addBezierVertex(float x2, float y2, float z2, float x3, float y3, float z3, - float x4, float y4, float z4, boolean brk) { + float x4, float y4, float z4, + boolean brk) { addVertex(x2, y2, z2, BEZIER_VERTEX, brk); addVertex(x3, y3, z3, -1, false); addVertex(x4, y4, z4, -1, false); } public void addQuadraticVertex(float cx, float cy, float cz, - float x3, float y3, float z3, boolean brk) { + float x3, float y3, float z3, + boolean brk) { addVertex(cx, cy, cz, QUADRATIC_VERTEX, brk); addVertex(x3, y3, z3, -1, false); } @@ -7763,9 +9040,9 @@ void addTrianglesEdges() { int i1 = 3 * i + 1; int i2 = 3 * i + 2; - addEdge(i0, i1, true, false); + addEdge(i0, i1, true, false); addEdge(i1, i2, false, false); - addEdge(i2, i0, false, false); + addEdge(i2, i0, false, false); closeEdge(i2, i0); } } @@ -7776,9 +9053,9 @@ void addTriangleFanEdges() { int i1 = i; int i2 = i + 1; - addEdge(i0, i1, true, false); + addEdge(i0, i1, true, false); addEdge(i1, i2, false, false); - addEdge(i2, i0, false, false); + addEdge(i2, i0, false, false); closeEdge(i2, i0); } } @@ -7795,9 +9072,9 @@ void addTriangleStripEdges() { i2 = i - 1; } - addEdge(i0, i1, true, false); + addEdge(i0, i1, true, false); addEdge(i1, i2, false, false); - addEdge(i2, i0, false, false); + addEdge(i2, i0, false, false); closeEdge(i2, i0); } } @@ -7809,10 +9086,10 @@ void addQuadsEdges() { int i2 = 4 * i + 2; int i3 = 4 * i + 3; - addEdge(i0, i1, true, false); + addEdge(i0, i1, true, false); addEdge(i1, i2, false, false); - addEdge(i2, i3, false, false); - addEdge(i3, i0, false, false); + addEdge(i2, i3, false, false); + addEdge(i3, i0, false, false); closeEdge(i3, i0); } } @@ -7824,10 +9101,10 @@ void addQuadStripEdges() { int i2 = 2 * qd + 1; int i3 = 2 * qd; - addEdge(i0, i1, true, false); + addEdge(i0, i1, true, false); addEdge(i1, i2, false, false); - addEdge(i2, i3, false, false); - addEdge(i3, i0, false, true); + addEdge(i2, i3, false, false); + addEdge(i3, i0, false, false); closeEdge(i3, i0); } } @@ -8201,7 +9478,8 @@ void addArc(float x, float y, float w, float h, addEdge(pidx, idx, i == 0, false); } else if (0 < i) { // when drawing full circle, the edge is closed later - addEdge(pidx, idx, i == inc, i == length && !fullCircle); + addEdge(pidx, idx, i == PApplet.min(inc, length), + i == length && !fullCircle); } } } while (i < length); @@ -8421,9 +9699,6 @@ int[] addSphere(float r, int detailU, int detailV, indices[indCount + 1] = vert1 - detailU; indices[indCount + 2] = vert1 - 1; indCount += 3; - - addEdge(vert1 - detailU, vert1 - 1, true, true); - addEdge(vert1 - 1, vert1, true, true); } // Northern cap ------------------------------------------------------- @@ -8447,7 +9722,6 @@ int[] addSphere(float r, int detailU, int detailV, indices[indCount + 3 * i + 1] = i0; indices[indCount + 3 * i + 2] = i0 + 1; - addEdge(i0, i0 + 1, true, true); addEdge(i0, i1, true, true); } indCount += 3 * detailU; @@ -8461,6 +9735,7 @@ int[] addSphere(float r, int detailU, int detailV, static protected class TessGeometry { int renderMode; PGraphicsOpenGL pg; + AttributeMap polyAttribs; // Tessellated polygon data int polyVertexCount; @@ -8478,6 +9753,9 @@ static protected class TessGeometry { IntBuffer polyEmissiveBuffer; FloatBuffer polyShininessBuffer; + // Generic attributes + HashMap polyAttribBuffers = new HashMap(); + int polyIndexCount; int firstPolyIndex; int lastPolyIndex; @@ -8531,8 +9809,13 @@ static protected class TessGeometry { float[] pointOffsets; short[] pointIndices; - TessGeometry(PGraphicsOpenGL pg, int mode) { + HashMap fpolyAttribs = new HashMap(); + HashMap ipolyAttribs = new HashMap(); + HashMap bpolyAttribs = new HashMap(); + + TessGeometry(PGraphicsOpenGL pg, AttributeMap attr, int mode) { this.pg = pg; + this.polyAttribs = attr; renderMode = mode; allocate(); } @@ -8585,6 +9868,22 @@ void allocate() { clear(); } + void initAttrib(VertexAttribute attrib) { + if (attrib.type == PGL.FLOAT && !fpolyAttribs.containsKey(attrib.name)) { + float[] temp = new float[attrib.tessSize * PGL.DEFAULT_TESS_VERTICES]; + fpolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + } else if (attrib.type == PGL.INT && !ipolyAttribs.containsKey(attrib.name)) { + int[] temp = new int[attrib.tessSize * PGL.DEFAULT_TESS_VERTICES]; + ipolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + } else if (attrib.type == PGL.BOOL && !bpolyAttribs.containsKey(attrib.name)) { + byte[] temp = new byte[attrib.tessSize * PGL.DEFAULT_TESS_VERTICES]; + bpolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + } + } + void clear() { firstPolyVertex = lastPolyVertex = polyVertexCount = 0; firstPolyIndex = lastPolyIndex = polyIndexCount = 0; @@ -8612,6 +9911,7 @@ void polyVertexCheck() { expandPolySpecular(newSize); expandPolyEmissive(newSize); expandPolyShininess(newSize); + expandAttributes(newSize); } firstPolyVertex = polyVertexCount; @@ -8632,6 +9932,7 @@ void polyVertexCheck(int count) { expandPolySpecular(newSize); expandPolyEmissive(newSize); expandPolyShininess(newSize); + expandAttributes(newSize); } firstPolyVertex = polyVertexCount; @@ -8885,6 +10186,30 @@ protected void updatePolyShininessBuffer(int offset, int size) { PGL.updateFloatBuffer(polyShininessBuffer, polyShininess, offset, size); } + protected void updateAttribBuffer(String name) { + updateAttribBuffer(name, 0, polyVertexCount); + } + + protected void updateAttribBuffer(String name, int offset, int size) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.type == PGL.FLOAT) { + FloatBuffer buffer = (FloatBuffer)polyAttribBuffers.get(name); + float[] array = fpolyAttribs.get(name); + PGL.updateFloatBuffer(buffer, array, + attrib.tessSize * offset, attrib.tessSize * size); + } else if (attrib.type == PGL.INT) { + IntBuffer buffer = (IntBuffer)polyAttribBuffers.get(name); + int[] array = ipolyAttribs.get(name); + PGL.updateIntBuffer(buffer, array, + attrib.tessSize * offset, attrib.tessSize * size); + } else if (attrib.type == PGL.BOOL) { + ByteBuffer buffer = (ByteBuffer)polyAttribBuffers.get(name); + byte[] array = bpolyAttribs.get(name); + PGL.updateByteBuffer(buffer, array, + attrib.tessSize * offset, attrib.tessSize * size); + } + } + protected void updatePolyIndicesBuffer() { updatePolyIndicesBuffer(0, polyIndexCount); } @@ -9021,6 +10346,43 @@ void expandPolyShininess(int n) { polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); } + void expandAttributes(int n) { + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.type == PGL.FLOAT) { + expandFloatAttribute(attrib, n); + } else if (attrib.type == PGL.INT) { + expandIntAttribute(attrib, n); + } else if (attrib.type == PGL.BOOL) { + expandBoolAttribute(attrib, n); + } + } + } + + void expandFloatAttribute(VertexAttribute attrib, int n) { + float[] array = fpolyAttribs.get(attrib.name); + float temp[] = new float[attrib.tessSize * n]; + PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); + fpolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + } + + void expandIntAttribute(VertexAttribute attrib, int n) { + int[] array = ipolyAttribs.get(attrib.name); + int temp[] = new int[attrib.tessSize * n]; + PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); + ipolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + } + + void expandBoolAttribute(VertexAttribute attrib, int n) { + byte[] array = bpolyAttribs.get(attrib.name); + byte temp[] = new byte[attrib.tessSize * n]; + PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); + bpolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + } + void expandPolyIndices(int n) { short temp[] = new short[n]; PApplet.arrayCopy(polyIndices, 0, temp, 0, polyIndexCount); @@ -9098,6 +10460,7 @@ void trim() { trimPolySpecular(); trimPolyEmissive(); trimPolyShininess(); + trimPolyAttributes(); } if (0 < polyIndexCount && polyIndexCount < polyIndices.length) { @@ -9181,6 +10544,43 @@ void trimPolyShininess() { polyShininessBuffer = PGL.allocateFloatBuffer(polyShininess); } + void trimPolyAttributes() { + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.type == PGL.FLOAT) { + trimFloatAttribute(attrib); + } else if (attrib.type == PGL.INT) { + trimIntAttribute(attrib); + } else if (attrib.type == PGL.BOOL) { + trimBoolAttribute(attrib); + } + } + } + + void trimFloatAttribute(VertexAttribute attrib) { + float[] array = fpolyAttribs.get(attrib.name); + float temp[] = new float[attrib.tessSize * polyVertexCount]; + PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); + fpolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateFloatBuffer(temp)); + } + + void trimIntAttribute(VertexAttribute attrib) { + int[] array = ipolyAttribs.get(attrib.name); + int temp[] = new int[attrib.tessSize * polyVertexCount]; + PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); + ipolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateIntBuffer(temp)); + } + + void trimBoolAttribute(VertexAttribute attrib) { + byte[] array = bpolyAttribs.get(attrib.name); + byte temp[] = new byte[attrib.tessSize * polyVertexCount]; + PApplet.arrayCopy(array, 0, temp, 0, attrib.tessSize * polyVertexCount); + bpolyAttribs.put(attrib.name, temp); + polyAttribBuffers.put(attrib.name, PGL.allocateByteBuffer(temp)); + } + void trimPolyIndices() { short temp[] = new short[polyIndexCount]; PApplet.arrayCopy(polyIndices, 0, temp, 0, polyIndexCount); @@ -9444,6 +10844,104 @@ void setLineVertex(int tessIdx, float[] vertices, int inIdx0, int inIdx1, // // Add poly geometry + void addPolyVertex(double[] d, boolean clampXY) { + int fcolor = + (int)d[ 3] << 24 | (int)d[ 4] << 16 | (int)d[ 5] << 8 | (int)d[ 6]; + int acolor = + (int)d[12] << 24 | (int)d[13] << 16 | (int)d[14] << 8 | (int)d[15]; + int scolor = + (int)d[16] << 24 | (int)d[17] << 16 | (int)d[18] << 8 | (int)d[19]; + int ecolor = + (int)d[20] << 24 | (int)d[21] << 16 | (int)d[22] << 8 | (int)d[23]; + + addPolyVertex((float)d[ 0], (float)d[ 1], (float)d[ 2], + fcolor, + (float)d[ 7], (float)d[ 8], (float)d[ 9], + (float)d[10], (float)d[11], + acolor, scolor, ecolor, + (float)d[24], + clampXY); + + if (25 < d.length) { + // Add the values of the custom attributes... + PMatrix3D mm = pg.modelview; + PMatrix3D nm = pg.modelviewInv; + int tessIdx = polyVertexCount - 1; + int index; + int pos = 25; + for (int i = 0; i < polyAttribs.size(); i++) { + VertexAttribute attrib = polyAttribs.get(i); + String name = attrib.name; + index = attrib.tessSize * tessIdx; + if (attrib.isColor()) { + // Reconstruct color from ARGB components + int color = + (int)d[pos + 0] << 24 | (int)d[pos + 1] << 16 | (int)d[pos + 2] << 8 | (int)d[pos + 3]; + int[] tessValues = ipolyAttribs.get(name); + tessValues[index] = color; + pos += 4; + } else if (attrib.isPosition()) { + float[] farray = fpolyAttribs.get(name); + float x = (float)d[pos++]; + float y = (float)d[pos++]; + float z = (float)d[pos++]; + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + if (clampXY) { + // ceil emulates the behavior of JAVA2D + farray[index++] = + PApplet.ceil(x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03); + farray[index++] = + PApplet.ceil(x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13); + } else { + farray[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; + farray[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; + } + farray[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; + farray[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + } else { + farray[index++] = x; + farray[index++] = y; + farray[index++] = z; + farray[index ] = 1; + } + } else if (attrib.isNormal()) { + float[] farray = fpolyAttribs.get(name); + float x = (float)d[pos + 0]; + float y = (float)d[pos + 1]; + float z = (float)d[pos + 2]; + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + farray[index++] = x*nm.m00 + y*nm.m10 + z*nm.m20; + farray[index++] = x*nm.m01 + y*nm.m11 + z*nm.m21; + farray[index ] = x*nm.m02 + y*nm.m12 + z*nm.m22; + } else { + farray[index++] = x; + farray[index++] = y; + farray[index ] = z; + } + pos += 3; + } else { + if (attrib.isFloat()) { + float[] farray = fpolyAttribs.get(name); + for (int n = 0; n < attrib.size; n++) { + farray[index++] = (float)d[pos++]; + } + } else if (attrib.isInt()) { + int[] iarray = ipolyAttribs.get(name); + for (int n = 0; n < attrib.size; n++) { + iarray[index++] = (int)d[pos++]; + } + } else if (attrib.isBool()) { + byte[] barray = bpolyAttribs.get(name); + for (int n = 0; n < attrib.size; n++) { + barray[index++] = (byte)d[pos++]; + } + } + pos += attrib.size; + } + } + } + } + void addPolyVertex(float x, float y, float z, int rgba, float nx, float ny, float nz, @@ -9531,124 +11029,273 @@ void addPolyVertex(InGeometry in, int i, boolean clampXY) { addPolyVertices(in, i, i, clampXY); } - void addPolyVertices(InGeometry in, int i0, int i1, boolean clampXY) { - int index; - int nvert = i1 - i0 + 1; + void addPolyVertices(InGeometry in, int i0, int i1, boolean clampXY) { + int index = 0; + int nvert = i1 - i0 + 1; + + polyVertexCheck(nvert); + + if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { + modelviewCoords(in, i0, index, nvert, clampXY); + } else { + if (nvert <= PGL.MIN_ARRAYCOPY_SIZE) { + copyFewCoords(in, i0, index, nvert); + } else { + copyManyCoords(in, i0, index, nvert); + } + } + + if (nvert <= PGL.MIN_ARRAYCOPY_SIZE) { + copyFewAttribs(in, i0, index, nvert); + } else { + copyManyAttribs(in, i0, index, nvert); + } + } + + // Apply modelview transformation on the vertices + private void modelviewCoords(InGeometry in, int i0, int index, int nvert, boolean clampXY) { + PMatrix3D mm = pg.modelview; + PMatrix3D nm = pg.modelviewInv; + + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + + index = 3 * inIdx; + float x = in.vertices[index++]; + float y = in.vertices[index++]; + float z = in.vertices[index ]; + + index = 3 * inIdx; + float nx = in.normals[index++]; + float ny = in.normals[index++]; + float nz = in.normals[index ]; + + index = 4 * tessIdx; + if (clampXY) { + // ceil emulates the behavior of JAVA2D + polyVertices[index++] = + PApplet.ceil(x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03); + polyVertices[index++] = + PApplet.ceil(x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13); + } else { + polyVertices[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; + polyVertices[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; + } + polyVertices[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; + polyVertices[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + + index = 3 * tessIdx; + polyNormals[index++] = nx*nm.m00 + ny*nm.m10 + nz*nm.m20; + polyNormals[index++] = nx*nm.m01 + ny*nm.m11 + nz*nm.m21; + polyNormals[index ] = nx*nm.m02 + ny*nm.m12 + nz*nm.m22; + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.isColor() || attrib.isOther()) continue; + + float[] inValues = in.fattribs.get(name); + index = 3 * inIdx; + x = inValues[index++]; + y = inValues[index++]; + z = inValues[index ]; + + float[] tessValues = fpolyAttribs.get(name); + if (attrib.isPosition()) { + index = 4 * tessIdx; + if (clampXY) { + // ceil emulates the behavior of JAVA2D + tessValues[index++] = + PApplet.ceil(x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03); + tessValues[index++] = + PApplet.ceil(x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13); + } else { + tessValues[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; + tessValues[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; + } + tessValues[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; + tessValues[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + } else { + index = 3 * tessIdx; + tessValues[index++] = x*nm.m00 + y*nm.m10 + z*nm.m20; + tessValues[index++] = x*nm.m01 + y*nm.m11 + z*nm.m21; + tessValues[index ] = x*nm.m02 + y*nm.m12 + z*nm.m22; + } + } + } + } + + // Just copy vertices one by one. + private void copyFewCoords(InGeometry in, int i0, int index, int nvert) { + // Copying elements one by one instead of using arrayCopy is more + // efficient for few vertices... + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; - polyVertexCheck(nvert); + index = 3 * inIdx; + float x = in.vertices[index++]; + float y = in.vertices[index++]; + float z = in.vertices[index ]; - if (renderMode == IMMEDIATE && pg.flushMode == FLUSH_WHEN_FULL) { - PMatrix3D mm = pg.modelview; - PMatrix3D nm = pg.modelviewInv; + index = 3 * inIdx; + float nx = in.normals[index++]; + float ny = in.normals[index++]; + float nz = in.normals[index ]; - for (int i = 0; i < nvert; i++) { - int inIdx = i0 + i; - int tessIdx = firstPolyVertex + i; + index = 4 * tessIdx; + polyVertices[index++] = x; + polyVertices[index++] = y; + polyVertices[index++] = z; + polyVertices[index ] = 1; - index = 3 * inIdx; - float x = in.vertices[index++]; - float y = in.vertices[index++]; - float z = in.vertices[index ]; + index = 3 * tessIdx; + polyNormals[index++] = nx; + polyNormals[index++] = ny; + polyNormals[index ] = nz; - index = 3 * inIdx; - float nx = in.normals[index++]; - float ny = in.normals[index++]; - float nz = in.normals[index ]; - - index = 4 * tessIdx; - if (clampXY) { - // ceil emulates the behavior of JAVA2D - polyVertices[index++] = - PApplet.ceil(x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03); - polyVertices[index++] = - PApplet.ceil(x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13); - } else { - polyVertices[index++] = x*mm.m00 + y*mm.m01 + z*mm.m02 + mm.m03; - polyVertices[index++] = x*mm.m10 + y*mm.m11 + z*mm.m12 + mm.m13; - } - polyVertices[index++] = x*mm.m20 + y*mm.m21 + z*mm.m22 + mm.m23; - polyVertices[index ] = x*mm.m30 + y*mm.m31 + z*mm.m32 + mm.m33; + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.isColor() || attrib.isOther()) continue; - index = 3 * tessIdx; - polyNormals[index++] = nx*nm.m00 + ny*nm.m10 + nz*nm.m20; - polyNormals[index++] = nx*nm.m01 + ny*nm.m11 + nz*nm.m21; - polyNormals[index ] = nx*nm.m02 + ny*nm.m12 + nz*nm.m22; - } - } else { - if (nvert <= PGL.MIN_ARRAYCOPY_SIZE) { - // Copying elements one by one instead of using arrayCopy is more - // efficient for few vertices... - for (int i = 0; i < nvert; i++) { - int inIdx = i0 + i; - int tessIdx = firstPolyVertex + i; - - index = 3 * inIdx; - float x = in.vertices[index++]; - float y = in.vertices[index++]; - float z = in.vertices[index ]; - - index = 3 * inIdx; - float nx = in.normals[index++]; - float ny = in.normals[index++]; - float nz = in.normals[index ]; + float[] inValues = in.fattribs.get(name); + index = 3 * inIdx; + x = inValues[index++]; + y = inValues[index++]; + z = inValues[index ]; + float[] tessValues = fpolyAttribs.get(name); + if (attrib.isPosition()) { index = 4 * tessIdx; - polyVertices[index++] = x; - polyVertices[index++] = y; - polyVertices[index++] = z; - polyVertices[index ] = 1; - + tessValues[index++] = x; + tessValues[index++] = y; + tessValues[index++] = z; + tessValues[index ] = 1; + } else { index = 3 * tessIdx; - polyNormals[index++] = nx; - polyNormals[index++] = ny; - polyNormals[index ] = nz; - } - } else { - for (int i = 0; i < nvert; i++) { - int inIdx = i0 + i; - int tessIdx = firstPolyVertex + i; - PApplet.arrayCopy(in.vertices, 3 * inIdx, - polyVertices, 4 * tessIdx, 3); - polyVertices[4 * tessIdx + 3] = 1; + tessValues[index++] = x; + tessValues[index++] = y; + tessValues[index ] = z; } - PApplet.arrayCopy(in.normals, 3 * i0, - polyNormals, 3 * firstPolyVertex, 3 * nvert); } } + } - if (nvert <= PGL.MIN_ARRAYCOPY_SIZE) { - for (int i = 0; i < nvert; i++) { - int inIdx = i0 + i; - int tessIdx = firstPolyVertex + i; - - index = 2 * inIdx; - float u = in.texcoords[index++]; - float v = in.texcoords[index ]; - - polyColors[tessIdx] = in.colors[inIdx]; - - index = 2 * tessIdx; - polyTexCoords[index++] = u; - polyTexCoords[index ] = v; - - polyAmbient[tessIdx] = in.ambient[inIdx]; - polySpecular[tessIdx] = in.specular[inIdx]; - polyEmissive[tessIdx] = in.emissive[inIdx]; - polyShininess[tessIdx] = in.shininess[inIdx]; + // Copy many vertices using arrayCopy + private void copyManyCoords(InGeometry in, int i0, int index, int nvert) { + for (int i = 0; i < nvert; i++) { + // Position data needs to be copied in batches of three, because the + // input vertices don't have a w coordinate. + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + PApplet.arrayCopy(in.vertices, 3 * inIdx, + polyVertices, 4 * tessIdx, 3); + polyVertices[4 * tessIdx + 3] = 1; + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (!attrib.isPosition()) continue; + float[] inValues = in.fattribs.get(name); + float[] tessValues = fpolyAttribs.get(name); + PApplet.arrayCopy(inValues, 3 * inIdx, + tessValues, 4 * tessIdx, 3); + tessValues[4 * tessIdx + 3] = 1; + } + } + PApplet.arrayCopy(in.normals, 3 * i0, + polyNormals, 3 * firstPolyVertex, 3 * nvert); + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (!attrib.isNormal()) continue; + float[] inValues = in.fattribs.get(name); + float[] tessValues = fpolyAttribs.get(name); + PApplet.arrayCopy(inValues, 3 * i0, + tessValues, 3 * firstPolyVertex, 3 * nvert); + } + } + + // Just copy attributes one by one. + private void copyFewAttribs(InGeometry in, int i0, int index, int nvert) { + for (int i = 0; i < nvert; i++) { + int inIdx = i0 + i; + int tessIdx = firstPolyVertex + i; + + index = 2 * inIdx; + float u = in.texcoords[index++]; + float v = in.texcoords[index ]; + + polyColors[tessIdx] = in.colors[inIdx]; + + index = 2 * tessIdx; + polyTexCoords[index++] = u; + polyTexCoords[index ] = v; + + polyAmbient[tessIdx] = in.ambient[inIdx]; + polySpecular[tessIdx] = in.specular[inIdx]; + polyEmissive[tessIdx] = in.emissive[inIdx]; + polyShininess[tessIdx] = in.shininess[inIdx]; + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.isPosition() || attrib.isNormal()) continue; + int index0 = attrib.size * inIdx; + int index1 = attrib.size * tessIdx; + if (attrib.isFloat()) { + float[] inValues = in.fattribs.get(name); + float[] tessValues = fpolyAttribs.get(name); + for (int n = 0; n < attrib.size; n++) { + tessValues[index1++] = inValues[index0++]; + } + } else if (attrib.isInt()) { + int[] inValues = in.iattribs.get(name); + int[] tessValues = ipolyAttribs.get(name); + for (int n = 0; n < attrib.size; n++) { + tessValues[index1++] = inValues[index0++]; + } + } else if (attrib.isBool()) { + byte[] inValues = in.battribs.get(name); + byte[] tessValues = bpolyAttribs.get(name); + for (int n = 0; n < attrib.size; n++) { + tessValues[index1++] = inValues[index0++]; + } + } } - } else { - PApplet.arrayCopy(in.colors, i0, - polyColors, firstPolyVertex, nvert); - PApplet.arrayCopy(in.texcoords, 2 * i0, - polyTexCoords, 2 * firstPolyVertex, 2 * nvert); - PApplet.arrayCopy(in.ambient, i0, - polyAmbient, firstPolyVertex, nvert); - PApplet.arrayCopy(in.specular, i0, - polySpecular, firstPolyVertex, nvert); - PApplet.arrayCopy(in.emissive, i0, - polyEmissive, firstPolyVertex, nvert); - PApplet.arrayCopy(in.shininess, i0, - polyShininess, firstPolyVertex, nvert); + } + } + + // Copy many attributes using arrayCopy() + private void copyManyAttribs(InGeometry in, int i0, int index, int nvert) { + PApplet.arrayCopy(in.colors, i0, + polyColors, firstPolyVertex, nvert); + PApplet.arrayCopy(in.texcoords, 2 * i0, + polyTexCoords, 2 * firstPolyVertex, 2 * nvert); + PApplet.arrayCopy(in.ambient, i0, + polyAmbient, firstPolyVertex, nvert); + PApplet.arrayCopy(in.specular, i0, + polySpecular, firstPolyVertex, nvert); + PApplet.arrayCopy(in.emissive, i0, + polyEmissive, firstPolyVertex, nvert); + PApplet.arrayCopy(in.shininess, i0, + polyShininess, firstPolyVertex, nvert); + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.isPosition() || attrib.isNormal()) continue; + Object inValues = null; + Object tessValues = null; + if (attrib.isFloat()) { + inValues = in.fattribs.get(name); + tessValues = fpolyAttribs.get(name); + } else if (attrib.isInt()) { + inValues = in.iattribs.get(name); + tessValues = ipolyAttribs.get(name); + } else if (attrib.isBool()) { + inValues = in.battribs.get(name); + tessValues = bpolyAttribs.get(name); + } + PApplet.arrayCopy(inValues, attrib.size * i0, + tessValues, attrib.tessSize * firstPolyVertex, + attrib.size * nvert); } } @@ -9700,6 +11347,27 @@ void applyMatrixOnPolyGeometry(PMatrix2D tr, int first, int last) { index = 3 * i; polyNormals[index++] = nx*tr.m00 + ny*tr.m01; polyNormals[index ] = nx*tr.m10 + ny*tr.m11; + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.isColor() || attrib.isOther()) continue; + float[] values = fpolyAttribs.get(name); + if (attrib.isPosition()) { + index = 4 * i; + x = values[index++]; + y = values[index ]; + index = 4 * i; + values[index++] = x*tr.m00 + y*tr.m01 + tr.m02; + values[index ] = x*tr.m10 + y*tr.m11 + tr.m12; + } else { + index = 3 * i; + nx = values[index++]; + ny = values[index ]; + index = 3 * i; + values[index++] = nx*tr.m00 + ny*tr.m01; + values[index ] = nx*tr.m10 + ny*tr.m11; + } + } } } } @@ -9708,17 +11376,15 @@ void applyMatrixOnLineGeometry(PMatrix2D tr, int first, int last) { if (first < last) { int index; + float scaleFactor = matrixScale(tr); for (int i = first; i <= last; i++) { index = 4 * i; float x = lineVertices[index++]; float y = lineVertices[index ]; index = 4 * i; - float xa = lineDirections[index++]; - float ya = lineDirections[index ]; - - float dx = xa - x; - float dy = ya - y; + float dx = lineDirections[index++]; + float dy = lineDirections[index ]; index = 4 * i; lineVertices[index++] = x*tr.m00 + y*tr.m01 + tr.m02; @@ -9727,6 +11393,7 @@ void applyMatrixOnLineGeometry(PMatrix2D tr, int first, int last) { index = 4 * i; lineDirections[index++] = dx*tr.m00 + dy*tr.m01; lineDirections[index ] = dx*tr.m10 + dy*tr.m11; + lineDirections[index + 2] *= scaleFactor; } } } @@ -9735,6 +11402,7 @@ void applyMatrixOnPointGeometry(PMatrix2D tr, int first, int last) { if (first < last) { int index; + float matrixScale = matrixScale(tr); for (int i = first; i <= last; i++) { index = 4 * i; float x = pointVertices[index++]; @@ -9743,6 +11411,10 @@ void applyMatrixOnPointGeometry(PMatrix2D tr, int first, int last) { index = 4 * i; pointVertices[index++] = x*tr.m00 + y*tr.m01 + tr.m02; pointVertices[index ] = x*tr.m10 + y*tr.m11 + tr.m12; + + index = 2 * i; + pointOffsets[index++] *= matrixScale; + pointOffsets[index] *= matrixScale; } } } @@ -9773,6 +11445,33 @@ void applyMatrixOnPolyGeometry(PMatrix3D tr, int first, int last) { polyNormals[index++] = nx*tr.m00 + ny*tr.m01 + nz*tr.m02; polyNormals[index++] = nx*tr.m10 + ny*tr.m11 + nz*tr.m12; polyNormals[index ] = nx*tr.m20 + ny*tr.m21 + nz*tr.m22; + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.isColor() || attrib.isOther()) continue; + float[] values = fpolyAttribs.get(name); + if (attrib.isPosition()) { + index = 4 * i; + x = values[index++]; + y = values[index++]; + z = values[index++]; + w = values[index ]; + index = 4 * i; + values[index++] = x*tr.m00 + y*tr.m01 + z*tr.m02 + w*tr.m03; + values[index++] = x*tr.m10 + y*tr.m11 + z*tr.m12 + w*tr.m13; + values[index++] = x*tr.m20 + y*tr.m21 + z*tr.m22 + w*tr.m23; + values[index ] = x*tr.m30 + y*tr.m31 + z*tr.m32 + w*tr.m33; + } else { + index = 3 * i; + nx = values[index++]; + ny = values[index++]; + nz = values[index ]; + index = 3 * i; + values[index++] = nx*tr.m00 + ny*tr.m01 + nz*tr.m02; + values[index++] = nx*tr.m10 + ny*tr.m11 + nz*tr.m12; + values[index ] = nx*tr.m20 + ny*tr.m21 + nz*tr.m22; + } + } } } } @@ -9781,6 +11480,7 @@ void applyMatrixOnLineGeometry(PMatrix3D tr, int first, int last) { if (first < last) { int index; + float scaleFactor = matrixScale(tr); for (int i = first; i <= last; i++) { index = 4 * i; float x = lineVertices[index++]; @@ -9789,13 +11489,9 @@ void applyMatrixOnLineGeometry(PMatrix3D tr, int first, int last) { float w = lineVertices[index ]; index = 4 * i; - float xa = lineDirections[index++]; - float ya = lineDirections[index++]; - float za = lineDirections[index ]; - - float dx = xa - x; - float dy = ya - y; - float dz = za - z; + float dx = lineDirections[index++]; + float dy = lineDirections[index++]; + float dz = lineDirections[index ]; index = 4 * i; lineVertices[index++] = x*tr.m00 + y*tr.m01 + z*tr.m02 + w*tr.m03; @@ -9806,7 +11502,8 @@ void applyMatrixOnLineGeometry(PMatrix3D tr, int first, int last) { index = 4 * i; lineDirections[index++] = dx*tr.m00 + dy*tr.m01 + dz*tr.m02; lineDirections[index++] = dx*tr.m10 + dy*tr.m11 + dz*tr.m12; - lineDirections[index ] = dx*tr.m20 + dy*tr.m21 + dz*tr.m22; + lineDirections[index++] = dx*tr.m20 + dy*tr.m21 + dz*tr.m22; + lineDirections[index] *= scaleFactor; } } } @@ -9815,6 +11512,7 @@ void applyMatrixOnPointGeometry(PMatrix3D tr, int first, int last) { if (first < last) { int index; + float matrixScale = matrixScale(tr); for (int i = first; i <= last; i++) { index = 4 * i; float x = pointVertices[index++]; @@ -9827,6 +11525,10 @@ void applyMatrixOnPointGeometry(PMatrix3D tr, int first, int last) { pointVertices[index++] = x*tr.m10 + y*tr.m11 + z*tr.m12 + w*tr.m13; pointVertices[index++] = x*tr.m20 + y*tr.m21 + z*tr.m22 + w*tr.m23; pointVertices[index ] = x*tr.m30 + y*tr.m31 + z*tr.m32 + w*tr.m33; + + index = 2 * i; + pointOffsets[index++] *= matrixScale; + pointOffsets[index] *= matrixScale; } } } @@ -9895,7 +11597,7 @@ public Tessellator() { void initGluTess() { if (gluTess == null) { - callback = new TessellatorCallback(); + callback = new TessellatorCallback(tess.polyAttribs); gluTess = pg.pgl.createTessellator(callback); } } @@ -10046,9 +11748,9 @@ void tessellateRoundPoints3D(int nvertTot, int nindTot, int nPtVert) { float inc = (float) SINCOS_LENGTH / perim; for (int k = 0; k < perim; k++) { tess.pointOffsets[2 * attribIdx + 0] = - 0.5f * cosLUT[(int) val] * strokeWeight; + 0.5f * cosLUT[(int) val] * transformScale() * strokeWeight; tess.pointOffsets[2 * attribIdx + 1] = - 0.5f * sinLUT[(int) val] * strokeWeight; + 0.5f * sinLUT[(int) val] * transformScale() * strokeWeight; val = (val + inc) % SINCOS_LENGTH; attribIdx++; } @@ -10079,6 +11781,7 @@ void tessellateRoundPoints2D(int nvertTot, int nindTot, int nPtVert) { IndexCache cache = tess.polyIndexCache; int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); firstPointIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. for (int i = 0; i < in.vertexCount; i++) { int count = cache.vertexCount[index]; if (PGL.MAX_VERTEX_INDEX1 <= count + nPtVert) { @@ -10175,9 +11878,9 @@ void tessellateSquarePoints3D(int nvertTot, int nindTot) { attribIdx++; for (int k = 0; k < 4; k++) { tess.pointOffsets[2 * attribIdx + 0] = - 0.5f * QUAD_POINT_SIGNS[k][0] * strokeWeight; + 0.5f * QUAD_POINT_SIGNS[k][0] * transformScale() * strokeWeight; tess.pointOffsets[2 * attribIdx + 1] = - 0.5f * QUAD_POINT_SIGNS[k][1] * strokeWeight; + 0.5f * QUAD_POINT_SIGNS[k][1] * transformScale() * strokeWeight; attribIdx++; } @@ -10207,6 +11910,7 @@ void tessellateSquarePoints2D(int nvertTot, int nindTot) { IndexCache cache = tess.polyIndexCache; int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); firstPointIndexCache = index; + if (firstPolyIndexCache == -1) firstPolyIndexCache = index; // If the geometry has no fill, needs the first poly index. for (int i = 0; i < in.vertexCount; i++) { int nvert = 5; int count = cache.vertexCount[index]; @@ -10283,16 +11987,24 @@ void tessellateLines3D(int lineCount) { // require 3 indices to specify their connectivities. int nind = lineCount * 2 * 3; + int vcount0 = tess.lineVertexCount; + int icount0 = tess.lineIndexCount; tess.lineVertexCheck(nvert); tess.lineIndexCheck(nind); int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : tess.lineIndexCache.getLast(); firstLineIndexCache = index; + int[] tmp = {0, 0}; + tess.lineIndexCache.setCounter(tmp); for (int ln = 0; ln < lineCount; ln++) { int i0 = 2 * ln + 0; int i1 = 2 * ln + 1; - index = addLineSegment3D(i0, i1, index, null, false); + index = addLineSegment3D(i0, i1, i0 - 2, i1 - 1, index, null, false); } + // Adjust counts of line vertices and indices to exact values + tess.lineIndexCache.setCounter(null); + tess.lineIndexCount = icount0 + tmp[0]; + tess.lineVertexCount = vcount0 + tmp[1]; lastLineIndexCache = index; } @@ -10361,9 +12073,11 @@ void tessellateLineStrip() { void tessellateLineStrip3D(int lineCount) { int nBevelTr = noCapsJoins() ? 0 : (lineCount - 1); - int nvert = lineCount * 4 + nBevelTr; + int nvert = lineCount * 4 + nBevelTr * 3; int nind = lineCount * 2 * 3 + nBevelTr * 2 * 3; + int vcount0 = tess.lineVertexCount; + int icount0 = tess.lineIndexCount; tess.lineVertexCheck(nvert); tess.lineIndexCheck(nind); int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : @@ -10371,15 +12085,21 @@ void tessellateLineStrip3D(int lineCount) { firstLineIndexCache = index; int i0 = 0; short[] lastInd = {-1, -1}; + int[] tmp = {0, 0}; + tess.lineIndexCache.setCounter(tmp); for (int ln = 0; ln < lineCount; ln++) { int i1 = ln + 1; if (0 < nBevelTr) { - index = addLineSegment3D(i0, i1, index, lastInd, false); + index = addLineSegment3D(i0, i1, i1 - 2, i1 - 1, index, lastInd, false); } else { - index = addLineSegment3D(i0, i1, index, null, false); + index = addLineSegment3D(i0, i1, i1 - 2, i1 - 1, index, null, false); } i0 = i1; } + // Adjust counts of line vertices and indices to exact values + tess.lineIndexCache.setCounter(null); + tess.lineIndexCount = icount0 + tmp[0]; + tess.lineVertexCount = vcount0 + tmp[1]; lastLineIndexCache = index; } @@ -10445,31 +12165,38 @@ void tessellateLineLoop() { void tessellateLineLoop3D(int lineCount) { int nBevelTr = noCapsJoins() ? 0 : lineCount; - int nvert = lineCount * 4 + nBevelTr; + int nvert = lineCount * 4 + nBevelTr * 3; int nind = lineCount * 2 * 3 + nBevelTr * 2 * 3; + int vcount0 = tess.lineVertexCount; + int icount0 = tess.lineIndexCount; tess.lineVertexCheck(nvert); tess.lineIndexCheck(nind); int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : tess.lineIndexCache.getLast(); firstLineIndexCache = index; int i0 = 0; + int i1 = -1; short[] lastInd = {-1, -1}; - short firstInd = -1; + int[] tmp = {0, 0}; + tess.lineIndexCache.setCounter(tmp); for (int ln = 0; ln < lineCount - 1; ln++) { - int i1 = ln + 1; + i1 = ln + 1; if (0 < nBevelTr) { - index = addLineSegment3D(i0, i1, index, lastInd, false); - if (ln == 0) firstInd = (short)(lastInd[0] - 2); + index = addLineSegment3D(i0, i1, i1 - 2, i1 - 1, index, lastInd, false); } else { - index = addLineSegment3D(i0, i1, index, null, false); + index = addLineSegment3D(i0, i1, i1 - 2, i1 - 1, index, null, false); } i0 = i1; } - index = addLineSegment3D(0, in.vertexCount - 1, index, lastInd, false); + index = addLineSegment3D(in.vertexCount - 1, 0, i1 - 2, i1 - 1, index, lastInd, false); if (0 < nBevelTr) { - index = addBevel3D(0, index, lastInd, firstInd, false); + index = addBevel3D(0, 1, in.vertexCount - 1, 0, index, lastInd, false); } + // Adjust counts of line vertices and indices to exact values + tess.lineIndexCache.setCounter(null); + tess.lineIndexCount = icount0 + tmp[0]; + tess.lineVertexCount = vcount0 + tmp[1]; lastLineIndexCache = index; } @@ -10538,32 +12265,53 @@ void tessellateEdges3D() { int nInVert = in.getNumEdgeVertices(bevel); int nInInd = in.getNumEdgeIndices(bevel); + int vcount0 = tess.lineVertexCount; + int icount0 = tess.lineIndexCount; tess.lineVertexCheck(nInVert); tess.lineIndexCheck(nInInd); int index = in.renderMode == RETAINED ? tess.lineIndexCache.addNew() : tess.lineIndexCache.getLast(); firstLineIndexCache = index; + int fi0 = 0; + int fi1 = 0; short[] lastInd = {-1, -1}; - short firstInd = -1; + int pi0 = -1; + int pi1 = -1; + + int[] tmp = {0, 0}; + tess.lineIndexCache.setCounter(tmp); for (int i = 0; i <= in.edgeCount - 1; i++) { int[] edge = in.edges[i]; int i0 = edge[0]; int i1 = edge[1]; if (bevel) { if (edge[2] == EDGE_CLOSE) { - index = addBevel3D(edge[1], index, lastInd, firstInd, false); - lastInd[0] = lastInd[1] = -1; // No join with next line segment. + index = addBevel3D(fi0, fi1, pi0, pi1, index, lastInd, false); } else { - index = addLineSegment3D(i0, i1, index, lastInd, false); - if (edge[2] == EDGE_START) firstInd = (short)(lastInd[0] - 2); - if (edge[2] == EDGE_STOP || edge[2] == EDGE_SINGLE) { - lastInd[0] = lastInd[1] = -1; // No join with next line segment. - } + index = addLineSegment3D(i0, i1, pi0, pi1, index, lastInd, false); } } else if (edge[2] != EDGE_CLOSE) { - index = addLineSegment3D(i0, i1, index, null, false); + index = addLineSegment3D(i0, i1, pi0, pi1, index, null, false); + } + if (edge[2] == EDGE_START) { + fi0 = i0; + fi1 = i1; + } + + if (edge[2] == EDGE_STOP || edge[2] == EDGE_SINGLE || edge[2] == EDGE_CLOSE) { + // No join with next line segment. + lastInd[0] = lastInd[1] = -1; + pi1 = pi0 = -1; + } else { + pi0 = i0; + pi1 = i1; } } + // Adjust counts of line vertices and indices to exact values + tess.lineIndexCache.setCounter(null); + tess.lineIndexCount = icount0 + tmp[0]; + tess.lineVertexCount = vcount0 + tmp[1]; + lastLineIndexCache = index; } @@ -10644,7 +12392,7 @@ boolean clampEdges2D() { // Adding the data that defines a quad starting at vertex i0 and // ending at i1. - int addLineSegment3D(int i0, int i1, int index, short[] lastInd, + int addLineSegment3D(int i0, int i1, int pi0, int pi1, int index, short[] lastInd, boolean constStroke) { IndexCache cache = tess.lineIndexCache; int count = cache.vertexCount[index]; @@ -10690,21 +12438,31 @@ int addLineSegment3D(int i0, int i1, int index, short[] lastInd, if (lastInd != null) { if (-1 < lastInd[0] && -1 < lastInd[1]) { // Adding bevel triangles - tess.setLineVertex(vidx, strokeVertices, i0, color0); - if (newCache) { - PGraphics.showWarning(TOO_LONG_STROKE_PATH_ERROR); - - // TODO: Fix this situation, the vertices from the previous cache - // block should be copied in the newly created one. - tess.lineIndices[iidx++] = (short) (count + 4); - tess.lineIndices[iidx++] = (short) (count + 0); - tess.lineIndices[iidx++] = (short) (count + 0); - - tess.lineIndices[iidx++] = (short) (count + 4); - tess.lineIndices[iidx++] = (short) (count + 1); - tess.lineIndices[iidx ] = (short) (count + 1); + if (-1 < pi0 && -1 < pi1) { + // Vertices used in the previous cache need to be copied to the + // newly created one + color = constStroke ? strokeColor : strokeColors[pi0]; + weight = constStroke ? strokeWeight : strokeWeights[pi0]; + weight *= transformScale(); + + tess.setLineVertex(vidx++, strokeVertices, pi1, color); + tess.setLineVertex(vidx++, strokeVertices, pi1, pi0, color, -weight/2); // count+2 vert from previous block + tess.setLineVertex(vidx, strokeVertices, pi1, pi0, color, +weight/2); // count+3 vert from previous block + + tess.lineIndices[iidx++] = (short) (count + 4); + tess.lineIndices[iidx++] = (short) (count + 5); + tess.lineIndices[iidx++] = (short) (count + 0); + + tess.lineIndices[iidx++] = (short) (count + 4); + tess.lineIndices[iidx++] = (short) (count + 6); + tess.lineIndices[iidx ] = (short) (count + 1); + + cache.incCounts(index, 6, 3); + } } else { + tess.setLineVertex(vidx, strokeVertices, i0, color0); + tess.lineIndices[iidx++] = (short) (count + 4); tess.lineIndices[iidx++] = lastInd[0]; tess.lineIndices[iidx++] = (short) (count + 0); @@ -10712,64 +12470,67 @@ int addLineSegment3D(int i0, int i1, int index, short[] lastInd, tess.lineIndices[iidx++] = (short) (count + 4); tess.lineIndices[iidx++] = lastInd[1]; tess.lineIndices[iidx ] = (short) (count + 1); - } - cache.incCounts(index, 6, 1); + cache.incCounts(index, 6, 1); + } } - // Vertices for next bevel + // The last two vertices of the segment will be used in the next + // bevel triangle lastInd[0] = (short) (count + 2); lastInd[1] = (short) (count + 3); } return index; } - int addBevel3D(int i0, int index, short[] lastInd, short firstInd, + int addBevel3D(int fi0, int fi1, int pi0 ,int pi1, int index, short[] lastInd, boolean constStroke) { IndexCache cache = tess.lineIndexCache; int count = cache.vertexCount[index]; - boolean addBevel = lastInd != null && -1 < lastInd[0] && -1 < lastInd[1]; boolean newCache = false; - if (PGL.MAX_VERTEX_INDEX1 <= count + (addBevel ? 1 : 0)) { + if (PGL.MAX_VERTEX_INDEX1 <= count + 3) { // We need to start a new index block for this line. index = cache.addNew(); count = 0; newCache = true; } + int iidx = cache.indexOffset[index] + cache.indexCount[index]; int vidx = cache.vertexOffset[index] + cache.vertexCount[index]; - int color0 = constStroke ? strokeColor : strokeColors[i0]; + int color = constStroke ? strokeColor : strokeColors[fi0]; + float weight = constStroke ? strokeWeight : strokeWeights[fi0]; + weight *= transformScale(); - if (lastInd != null) { - if (-1 < lastInd[0] && -1 < lastInd[1]) { - tess.setLineVertex(vidx, strokeVertices, i0, color0); + tess.setLineVertex(vidx++, strokeVertices, fi0, color); + tess.setLineVertex(vidx++, strokeVertices, fi0, fi1, color, +weight/2); + tess.setLineVertex(vidx++, strokeVertices, fi0, fi1, color, -weight/2); - if (newCache) { - PGraphics.showWarning(TOO_LONG_STROKE_PATH_ERROR); - - // TODO: Fix this situation, the vertices from the previous cache - // block should be copied in the newly created one. -// tess.lineIndices[iidx++] = (short) (count + 4); -// tess.lineIndices[iidx++] = (short) (count + 0); -// tess.lineIndices[iidx++] = (short) (count + 0); -// -// tess.lineIndices[iidx++] = (short) (count + 4); -// tess.lineIndices[iidx++] = (short) (count + 1); -// tess.lineIndices[iidx ] = (short) (count + 1); - } else { - tess.lineIndices[iidx++] = (short) (count + 0); - tess.lineIndices[iidx++] = lastInd[0]; - tess.lineIndices[iidx++] = (short) (firstInd + 0); + int extra = 0; + if (newCache && -1 < pi0 && -1 < pi1) { + // Vertices used in the previous cache need to be copied to the + // newly created one + color = constStroke ? strokeColor : strokeColors[pi1]; + weight = constStroke ? strokeWeight : strokeWeights[pi1]; + weight *= transformScale(); - tess.lineIndices[iidx++] = (short) (count + 0); - tess.lineIndices[iidx++] = lastInd[1]; - tess.lineIndices[iidx ] = (short) (firstInd + 1); - } + tess.setLineVertex(vidx++, strokeVertices, pi1, pi0, color, -weight/2); + tess.setLineVertex(vidx , strokeVertices, pi1, pi0, color, +weight/2); - cache.incCounts(index, 6, 1); - } + lastInd[0] = (short) (count + 3); + lastInd[1] = (short) (count + 4); + extra = 2; } + tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = lastInd[0]; + tess.lineIndices[iidx++] = (short) (count + 1); + + tess.lineIndices[iidx++] = (short) (count + 0); + tess.lineIndices[iidx++] = (short) (count + 2); + tess.lineIndices[iidx ] = lastInd[1]; + + cache.incCounts(index, 6, 3 + extra); + return index; } @@ -10909,28 +12670,7 @@ boolean noCapsJoins() { float transformScale() { if (-1 < transformScale) return transformScale; - - // Volumetric scaling factor that is associated to the current - // transformation matrix, which is given by the absolute value of its - // determinant: - float factor = 1; - - if (transform != null) { - if (transform instanceof PMatrix2D) { - PMatrix2D tr = (PMatrix2D)transform; - float areaScaleFactor = Math.abs(tr.m00 * tr.m11 - tr.m01 * tr.m10); - factor = (float) Math.sqrt(areaScaleFactor); - } else if (transform instanceof PMatrix3D) { - PMatrix3D tr = (PMatrix3D)transform; - float volumeScaleFactor = - Math.abs(tr.m00 * (tr.m11 * tr.m22 - tr.m12 * tr.m21) + - tr.m01 * (tr.m12 * tr.m20 - tr.m10 * tr.m22) + - tr.m02 * (tr.m10 * tr.m21 - tr.m11 * tr.m20)); - factor = (float) Math.pow(volumeScaleFactor, 1.0f / 3.0f); - } - } - - return transformScale = factor; + return transformScale = matrixScale(transform); } boolean segmentIsAxisAligned(int i0, int i1) { @@ -11222,7 +12962,7 @@ void splitRawIndices(boolean clamp) { IndexCache cache = tess.polyIndexCache; // In retained mode, each shape has with its own cache item, since - // they should always be available to be rendererd individually, even + // they should always be available to be rendered individually, even // if contained in a larger hierarchy. int index = in.renderMode == RETAINED ? cache.addNew() : cache.getLast(); firstPolyIndexCache = index; @@ -11413,8 +13153,8 @@ void setLastTexIndex(int lastIndex, int lastCache) { } else { texCache.setLastIndex(lastIndex, lastCache); } + prevTexImage = newTexImage; } - prevTexImage = newTexImage; } // ----------------------------------------------------------------- @@ -11521,43 +13261,7 @@ void addBezierVertex(int i) { strokeWeight = in.strokeWeights[i]; } - int fcol = 0, fa = 0, fr = 0, fg = 0, fb = 0; - int acol = 0, aa = 0, ar = 0, ag = 0, ab = 0; - int scol = 0, sa = 0, sr = 0, sg = 0, sb = 0; - int ecol = 0, ea = 0, er = 0, eg = 0, eb = 0; - float nx = 0, ny = 0, nz = 0, u = 0, v = 0, sh = 0; - if (fill) { - fcol = in.colors[i]; - fa = (fcol >> 24) & 0xFF; - fr = (fcol >> 16) & 0xFF; - fg = (fcol >> 8) & 0xFF; - fb = (fcol >> 0) & 0xFF; - - acol = in.ambient[i]; - aa = (acol >> 24) & 0xFF; - ar = (acol >> 16) & 0xFF; - ag = (acol >> 8) & 0xFF; - ab = (acol >> 0) & 0xFF; - - scol = in.specular[i]; - sa = (scol >> 24) & 0xFF; - sr = (scol >> 16) & 0xFF; - sg = (scol >> 8) & 0xFF; - sb = (scol >> 0) & 0xFF; - - ecol = in.emissive[i]; - ea = (ecol >> 24) & 0xFF; - er = (ecol >> 16) & 0xFF; - eg = (ecol >> 8) & 0xFF; - eb = (ecol >> 0) & 0xFF; - - nx = in.normals[3*i + 0]; - ny = in.normals[3*i + 1]; - nz = in.normals[3*i + 2]; - u = in.texcoords[2*i + 0]; - v = in.texcoords[2*i + 1]; - sh = in.shininess[i]; - } + double[] vertexT = fill ? collectVertexAttributes(i) : null; float x2 = in.vertices[3*i + 0]; float y2 = in.vertices[3*i + 1]; @@ -11586,12 +13290,10 @@ void addBezierVertex(int i) { y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; if (fill) { - double[] vertex = new double[] { - x1, y1, z1, - fa, fr, fg, fb, - nx, ny, nz, - u, v, - aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + double[] vertex = Arrays.copyOf(vertexT, vertexT.length); + vertex[0] = x1; + vertex[1] = y1; + vertex[2] = z1; gluTess.addVertex(vertex); } if (stroke) addStrokeVertex(x1, y1, z1, strokeColor, strokeWeight); @@ -11617,43 +13319,7 @@ void addQuadraticVertex(int i) { strokeWeight = in.strokeWeights[i]; } - int fcol = 0, fa = 0, fr = 0, fg = 0, fb = 0; - int acol = 0, aa = 0, ar = 0, ag = 0, ab = 0; - int scol = 0, sa = 0, sr = 0, sg = 0, sb = 0; - int ecol = 0, ea = 0, er = 0, eg = 0, eb = 0; - float nx = 0, ny = 0, nz = 0, u = 0, v = 0, sh = 0; - if (fill) { - fcol = in.colors[i]; - fa = (fcol >> 24) & 0xFF; - fr = (fcol >> 16) & 0xFF; - fg = (fcol >> 8) & 0xFF; - fb = (fcol >> 0) & 0xFF; - - acol = in.ambient[i]; - aa = (acol >> 24) & 0xFF; - ar = (acol >> 16) & 0xFF; - ag = (acol >> 8) & 0xFF; - ab = (acol >> 0) & 0xFF; - - scol = in.specular[i]; - sa = (scol >> 24) & 0xFF; - sr = (scol >> 16) & 0xFF; - sg = (scol >> 8) & 0xFF; - sb = (scol >> 0) & 0xFF; - - ecol = in.emissive[i]; - ea = (ecol >> 24) & 0xFF; - er = (ecol >> 16) & 0xFF; - eg = (ecol >> 8) & 0xFF; - eb = (ecol >> 0) & 0xFF; - - nx = in.normals[3*i + 0]; - ny = in.normals[3*i + 1]; - nz = in.normals[3*i + 2]; - u = in.texcoords[2*i + 0]; - v = in.texcoords[2*i + 1]; - sh = in.shininess[i]; - } + double[] vertexT = fill ? collectVertexAttributes(i) : null; float cx = in.vertices[3*i + 0]; float cy = in.vertices[3*i + 1]; @@ -11689,12 +13355,10 @@ void addQuadraticVertex(int i) { y1 += yplot1; yplot1 += yplot2; yplot2 += yplot3; z1 += zplot1; zplot1 += zplot2; zplot2 += zplot3; if (fill) { - double[] vertex = new double[] { - x1, y1, z1, - fa, fr, fg, fb, - nx, ny, nz, - u, v, - aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + double[] vertex = Arrays.copyOf(vertexT, vertexT.length); + vertex[0] = x1; + vertex[1] = y1; + vertex[2] = z1; gluTess.addVertex(vertex); } if (stroke) addStrokeVertex(x1, y1, z1, strokeColor, strokeWeight); @@ -11711,6 +13375,10 @@ void addCurveVertex(int i) { pg.curveVertexCount++; // draw a segment if there are enough points + if (pg.curveVertexCount == 3) { + float[] v = pg.curveVertices[pg.curveVertexCount - 2]; + addCurveInitialVertex(i, v[X], v[Y], v[Z]); + } if (pg.curveVertexCount > 3) { float[] v1 = pg.curveVertices[pg.curveVertexCount - 4]; float[] v2 = pg.curveVertices[pg.curveVertexCount - 3]; @@ -11723,6 +13391,19 @@ void addCurveVertex(int i) { } } + void addCurveInitialVertex(int i, float x, float y, float z) { + if (fill) { + double[] vertex0 = collectVertexAttributes(i); + vertex0[0] = x; + vertex0[1] = y; + vertex0[2] = z; + gluTess.addVertex(vertex0); + } + if (stroke) { + addStrokeVertex(x, y, z, in.strokeColors[i], strokeWeight); + } + } + void addCurveVertexSegment(int i, float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3, @@ -11734,43 +13415,7 @@ void addCurveVertexSegment(int i, float x1, float y1, float z1, strokeWeight = in.strokeWeights[i]; } - int fcol = 0, fa = 0, fr = 0, fg = 0, fb = 0; - int acol = 0, aa = 0, ar = 0, ag = 0, ab = 0; - int scol = 0, sa = 0, sr = 0, sg = 0, sb = 0; - int ecol = 0, ea = 0, er = 0, eg = 0, eb = 0; - float nx = 0, ny = 0, nz = 0, u = 0, v = 0, sh = 0; - if (fill) { - fcol = in.colors[i]; - fa = (fcol >> 24) & 0xFF; - fr = (fcol >> 16) & 0xFF; - fg = (fcol >> 8) & 0xFF; - fb = (fcol >> 0) & 0xFF; - - acol = in.ambient[i]; - aa = (acol >> 24) & 0xFF; - ar = (acol >> 16) & 0xFF; - ag = (acol >> 8) & 0xFF; - ab = (acol >> 0) & 0xFF; - - scol = in.specular[i]; - sa = (scol >> 24) & 0xFF; - sr = (scol >> 16) & 0xFF; - sg = (scol >> 8) & 0xFF; - sb = (scol >> 0) & 0xFF; - - ecol = in.emissive[i]; - ea = (ecol >> 24) & 0xFF; - er = (ecol >> 16) & 0xFF; - eg = (ecol >> 8) & 0xFF; - eb = (ecol >> 0) & 0xFF; - - nx = in.normals[3*i + 0]; - ny = in.normals[3*i + 1]; - nz = in.normals[3*i + 2]; - u = in.texcoords[2*i + 0]; - v = in.texcoords[2*i + 1]; - sh = in.shininess[i]; - } + double[] vertexT = fill ? collectVertexAttributes(i) : null; float x = x2; float y = y2; @@ -11790,28 +13435,15 @@ void addCurveVertexSegment(int i, float x1, float y1, float z1, float zplot2 = draw.m20*z1 + draw.m21*z2 + draw.m22*z3 + draw.m23*z4; float zplot3 = draw.m30*z1 + draw.m31*z2 + draw.m32*z3 + draw.m33*z4; - if (fill) { - double[] vertex0 = new double[] { - x, y, z, - fa, fr, fg, fb, - nx, ny, nz, - u, v, - aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; - gluTess.addVertex(vertex0); - } - if (stroke) addStrokeVertex(x, y, z, strokeColor, strokeWeight); - for (int j = 0; j < pg.curveDetail; j++) { x += xplot1; xplot1 += xplot2; xplot2 += xplot3; y += yplot1; yplot1 += yplot2; yplot2 += yplot3; z += zplot1; zplot1 += zplot2; zplot2 += zplot3; if (fill) { - double[] vertex1 = new double[] { - x, y, z, - fa, fr, fg, fb, - nx, ny, nz, - u, v, - aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + double[] vertex1 = Arrays.copyOf(vertexT, vertexT.length); + vertex1[0] = x; + vertex1[1] = y; + vertex1[2] = z; gluTess.addVertex(vertex1); } if (stroke) addStrokeVertex(x, y, z, strokeColor, strokeWeight); @@ -11825,55 +13457,64 @@ void addVertex(int i) { float y = in.vertices[3*i + 1]; float z = in.vertices[3*i + 2]; - int strokeColor = 0; - float strokeWeight = 0; - if (stroke) { - strokeColor = in.strokeColors[i]; - strokeWeight = in.strokeWeights[i]; - } - if (fill) { - // Separating colors into individual rgba components for interpolation. - int fcol = in.colors[i]; - int fa = (fcol >> 24) & 0xFF; - int fr = (fcol >> 16) & 0xFF; - int fg = (fcol >> 8) & 0xFF; - int fb = (fcol >> 0) & 0xFF; - - int acol = in.ambient[i]; - int aa = (acol >> 24) & 0xFF; - int ar = (acol >> 16) & 0xFF; - int ag = (acol >> 8) & 0xFF; - int ab = (acol >> 0) & 0xFF; - - int scol = in.specular[i]; - int sa = (scol >> 24) & 0xFF; - int sr = (scol >> 16) & 0xFF; - int sg = (scol >> 8) & 0xFF; - int sb = (scol >> 0) & 0xFF; - - int ecol = in.emissive[i]; - int ea = (ecol >> 24) & 0xFF; - int er = (ecol >> 16) & 0xFF; - int eg = (ecol >> 8) & 0xFF; - int eb = (ecol >> 0) & 0xFF; - - float nx = in.normals[3*i + 0]; - float ny = in.normals[3*i + 1]; - float nz = in.normals[3*i + 2]; - float u = in.texcoords[2*i + 0]; - float v = in.texcoords[2*i + 1]; - float sh = in.shininess[i]; - - double[] vertex = new double[] { - x, y, z, - fa, fr, fg, fb, - nx, ny, nz, - u, v, - aa, ar, ag, ab, sa, sr, sg, sb, ea, er, eg, eb, sh}; + double[] vertex = collectVertexAttributes(i); + vertex[0] = x; + vertex[1] = y; + vertex[2] = z; gluTess.addVertex(vertex); } - if (stroke) addStrokeVertex(x, y, z, strokeColor, strokeWeight); + if (stroke) { + addStrokeVertex(x, y, z, in.strokeColors[i], in.strokeWeights[i]); + } + } + + double[] collectVertexAttributes(int i) { + final int COORD_COUNT = 3; + final int ATTRIB_COUNT = 22; + + double[] avect = in.getAttribVector(i); + + double[] r = new double[COORD_COUNT + ATTRIB_COUNT + avect.length]; + + int j = COORD_COUNT; + + int fcol = in.colors[i]; + r[j++] = (fcol >> 24) & 0xFF; // fa + r[j++] = (fcol >> 16) & 0xFF; // fr + r[j++] = (fcol >> 8) & 0xFF; // fg + r[j++] = (fcol >> 0) & 0xFF; // fb + + r[j++] = in.normals[3*i + 0]; // nx + r[j++] = in.normals[3*i + 1]; // ny + r[j++] = in.normals[3*i + 2]; // nz + + r[j++] = in.texcoords[2*i + 0]; // u + r[j++] = in.texcoords[2*i + 1]; // v + + int acol = in.ambient[i]; + r[j++] = (acol >> 24) & 0xFF; // aa + r[j++] = (acol >> 16) & 0xFF; // ar + r[j++] = (acol >> 8) & 0xFF; // ag + r[j++] = (acol >> 0) & 0xFF; // ab + + int scol = in.specular[i]; + r[j++] = (scol >> 24) & 0xFF; // sa + r[j++] = (scol >> 16) & 0xFF; // sr + r[j++] = (scol >> 8) & 0xFF; // sg + r[j++] = (scol >> 0) & 0xFF; // sb + + int ecol = in.emissive[i]; + r[j++] = (ecol >> 24) & 0xFF; // ea + r[j++] = (ecol >> 16) & 0xFF; // er + r[j++] = (ecol >> 8) & 0xFF; // eg + r[j++] = (ecol >> 0) & 0xFF; // eb + + r[j++] = in.shininess[i]; // sh + + System.arraycopy(avect, 0, r, j, avect.length); + + return r; } void beginPolygonStroke() { @@ -12040,6 +13681,7 @@ boolean clampLinePath() { // http://code.google.com/p/glues/ // to eventually come up with an optimized GLU tessellator in native code. protected class TessellatorCallback implements PGL.TessellatorCallback { + AttributeMap attribs; boolean calcNormals; boolean strokeTess; boolean clampXY; @@ -12050,6 +13692,10 @@ protected class TessellatorCallback implements PGL.TessellatorCallback { int vertOffset; int primitive; + public TessellatorCallback(AttributeMap attribs) { + this.attribs = attribs; + } + public void init(boolean addCache, boolean strokeTess, boolean calcNorm, boolean clampXY) { this.strokeTess = strokeTess; @@ -12160,28 +13806,12 @@ public void vertex(Object data) { double[] d = (double[]) data; int l = d.length; if (l < 25) { - throw new RuntimeException("TessCallback vertex() data is not " + - "of length 25"); + throw new RuntimeException("TessCallback vertex() data is " + + "too small"); } if (vertCount < PGL.MAX_VERTEX_INDEX1) { - // Combining individual rgba components back into int color values - int fcolor = - ((int)d[ 3]<<24) | ((int)d[ 4]<<16) | ((int)d[ 5]<<8) | (int)d[ 6]; - int acolor = - ((int)d[12]<<24) | ((int)d[13]<<16) | ((int)d[14]<<8) | (int)d[15]; - int scolor = - ((int)d[16]<<24) | ((int)d[17]<<16) | ((int)d[18]<<8) | (int)d[19]; - int ecolor = - ((int)d[20]<<24) | ((int)d[21]<<16) | ((int)d[22]<<8) | (int)d[23]; - - tess.addPolyVertex((float) d[ 0], (float) d[ 1], (float) d[ 2], - fcolor, - (float) d[ 7], (float) d[ 8], (float) d[ 9], - (float) d[10], (float) d[11], - acolor, scolor, ecolor, - (float) d[24], clampXY); - + tess.addPolyVertex(d, clampXY); vertCount++; } else { throw new RuntimeException("The tessellator is generating too " + @@ -12215,7 +13845,8 @@ public void error(int errnum) { */ public void combine(double[] coords, Object[] data, float[] weight, Object[] outData) { - double[] vertex = new double[25 + 8]; + int n = ((double[])data[0]).length; + double[] vertex = new double[n]; vertex[0] = coords[0]; vertex[1] = coords[1]; vertex[2] = coords[2]; @@ -12223,7 +13854,7 @@ public void combine(double[] coords, Object[] data, // Calculating the rest of the vertex parameters (color, // normal, texcoords) as the linear combination of the // combined vertices. - for (int i = 3; i < 25; i++) { + for (int i = 3; i < n; i++) { vertex[i] = 0; for (int j = 0; j < 4; j++) { double[] vertData = (double[])data[j]; @@ -12233,19 +13864,417 @@ public void combine(double[] coords, Object[] data, } } - // Normalizing normal vector, since the weighted + // Normalizing normal vectors, since the weighted // combination of normal vectors is not necessarily // normal. - double sum = vertex[7] * vertex[7] + - vertex[8] * vertex[8] + - vertex[9] * vertex[9]; - double len = Math.sqrt(sum); - vertex[7] /= len; - vertex[8] /= len; - vertex[9] /= len; + normalize(vertex, 7); + if (25 < n) { + // We have custom attributes, look for normal attributes + int pos = 25; + for (int i = 0; i < attribs.size(); i++) { + VertexAttribute attrib = attribs.get(i); + if (attrib.isNormal()) { + normalize(vertex, pos); + pos += 3; + } else { + pos += attrib.size; + } + } + } outData[0] = vertex; } + + private void normalize(double[] v, int i) { + double sum = v[i ] * v[i ] + + v[i + 1] * v[i + 1] + + v[i + 2] * v[i + 2]; + double len = Math.sqrt(sum); + if (0 < len) { + v[i ] /= len; + v[i + 1] /= len; + v[i + 2] /= len; + } + } + } + } + + + static protected class DepthSorter { + + static final int X = 0; + static final int Y = 1; + static final int Z = 2; + static final int W = 3; + + static final int X0 = 0; + static final int Y0 = 1; + static final int Z0 = 2; + static final int X1 = 3; + static final int Y1 = 4; + static final int Z1 = 5; + static final int X2 = 6; + static final int Y2 = 7; + static final int Z2 = 8; + + int[] triangleIndices = new int[0]; + int[] texMap = new int[0]; + int[] voffsetMap = new int[0]; + + float[] minXBuffer = new float[0]; + float[] minYBuffer = new float[0]; + float[] minZBuffer = new float[0]; + float[] maxXBuffer = new float[0]; + float[] maxYBuffer = new float[0]; + float[] maxZBuffer = new float[0]; + + float[] screenVertices = new float[0]; + + float[] triA = new float[9]; + float[] triB = new float[9]; + + BitSet marked = new BitSet(); + BitSet swapped = new BitSet(); + + PGraphicsOpenGL pg; + + DepthSorter (PGraphicsOpenGL pg) { + this.pg = pg; + } + + void checkIndexBuffers(int newTriangleCount) { + if (triangleIndices.length < newTriangleCount) { + int newSize = (newTriangleCount / 4 + 1) * 5; + triangleIndices = new int[newSize]; + texMap = new int[newSize]; + voffsetMap = new int[newSize]; + minXBuffer = new float[newSize]; + minYBuffer = new float[newSize]; + minZBuffer = new float[newSize]; + maxXBuffer = new float[newSize]; + maxYBuffer = new float[newSize]; + maxZBuffer = new float[newSize]; + } + } + + void checkVertexBuffer(int newVertexCount) { + int coordCount = 3*newVertexCount; + if (screenVertices.length < coordCount) { + int newSize = (coordCount / 4 + 1) * 5; + screenVertices = new float[newSize]; + } + } + + // Sorting -------------------------------------------- + + void sort(TessGeometry tessGeo) { + + int triangleCount = tessGeo.polyIndexCount / 3; + checkIndexBuffers(triangleCount); + int[] triangleIndices = this.triangleIndices; + int[] texMap = this.texMap; + int[] voffsetMap = this.voffsetMap; + + { // Initialize triangle indices + for (int i = 0; i < triangleCount; i++) { + triangleIndices[i] = i; + } + } + + { // Map caches to triangles + TexCache texCache = pg.texCache; + IndexCache indexCache = tessGeo.polyIndexCache; + for (int i = 0; i < texCache.size; i++) { + int first = texCache.firstCache[i]; + int last = texCache.lastCache[i]; + for (int n = first; n <= last; n++) { + int ioffset = n == first + ? texCache.firstIndex[i] + : indexCache.indexOffset[n]; + int icount = n == last + ? texCache.lastIndex[i] - ioffset + 1 + : indexCache.indexOffset[n] + indexCache.indexCount[n] - ioffset; + + for (int tr = ioffset / 3; tr < (ioffset + icount) / 3; tr++) { + texMap[tr] = i; + voffsetMap[tr] = n; + } + } + } + } + + { // Map vertices to screen + int polyVertexCount = tessGeo.polyVertexCount; + checkVertexBuffer(polyVertexCount); + float[] screenVertices = this.screenVertices; + + float[] polyVertices = tessGeo.polyVertices; + + PMatrix3D projection = pg.projection; + + for (int i = 0; i < polyVertexCount; i++) { + float x = polyVertices[4*i+X]; + float y = polyVertices[4*i+Y]; + float z = polyVertices[4*i+Z]; + float w = polyVertices[4*i+W]; + + float ox = projection.m00 * x + projection.m01 * y + + projection.m02 * z + projection.m03 * w; + float oy = projection.m10 * x + projection.m11 * y + + projection.m12 * z + projection.m13 * w; + float oz = projection.m20 * x + projection.m21 * y + + projection.m22 * z + projection.m23 * w; + float ow = projection.m30 * x + projection.m31 * y + + projection.m32 * z + projection.m33 * w; + if (nonZero(ow)) { + ox /= ow; + oy /= ow; + oz /= ow; + } + screenVertices[3*i+X] = ox; + screenVertices[3*i+Y] = oy; + screenVertices[3*i+Z] = -oz; + } + } + float[] screenVertices = this.screenVertices; + + int[] vertexOffset = tessGeo.polyIndexCache.vertexOffset; + short[] polyIndices = tessGeo.polyIndices; + + float[] triA = this.triA; + float[] triB = this.triB; + + for (int i = 0; i < triangleCount; i++) { + fetchTriCoords(triA, i, vertexOffset, voffsetMap, screenVertices, polyIndices); + minXBuffer[i] = PApplet.min(triA[X0], triA[X1], triA[X2]); + maxXBuffer[i] = PApplet.max(triA[X0], triA[X1], triA[X2]); + minYBuffer[i] = PApplet.min(triA[Y0], triA[Y1], triA[Y2]); + maxYBuffer[i] = PApplet.max(triA[Y0], triA[Y1], triA[Y2]); + minZBuffer[i] = PApplet.min(triA[Z0], triA[Z1], triA[Z2]); + maxZBuffer[i] = PApplet.max(triA[Z0], triA[Z1], triA[Z2]); + } + + sortByMinZ(0, triangleCount - 1, triangleIndices, minZBuffer); + + int activeTid = 0; + + BitSet marked = this.marked; + BitSet swapped = this.swapped; + + marked.clear(); + + while (activeTid < triangleCount) { + int testTid = activeTid + 1; + boolean draw = false; + + swapped.clear(); + + int ati = triangleIndices[activeTid]; + float minXA = minXBuffer[ati]; + float maxXA = maxXBuffer[ati]; + float minYA = minYBuffer[ati]; + float maxYA = maxYBuffer[ati]; + float maxZA = maxZBuffer[ati]; + + fetchTriCoords(triA, ati, vertexOffset, voffsetMap, screenVertices, polyIndices); + + while (!draw && testTid < triangleCount) { + int tti = triangleIndices[testTid]; + + // TEST 1 // Z overlap + if (maxZA <= minZBuffer[tti] && !marked.get(tti)) { + draw = true; // pass, not overlapping in Z, draw it + + // TEST 2 // XY overlap using square window + } else if (maxXA <= minXBuffer[tti] || maxYA <= minYBuffer[tti] || + minXA >= maxXBuffer[tti] || minYA >= maxYBuffer[tti]) { + testTid++; // pass, not overlapping in XY + + // TEST 3 // test on which side ACTIVE is relative to TEST + } else { + fetchTriCoords(triB, tti, vertexOffset, voffsetMap, + screenVertices, polyIndices); + if (side(triB, triA, -1) > 0) { + testTid++; // pass, ACTIVE is in halfspace behind current TEST + + // TEST 4 // test on which side TEST is relative to ACTIVE + } else if (side(triA, triB, 1) > 0) { + testTid++; // pass, current TEST is in halfspace in front of ACTIVE + + // FAIL, wrong depth order, swap + } else { + if (!swapped.get(tti)) { + swapped.set(ati); + marked.set(tti); + rotateRight(triangleIndices, activeTid, testTid); + + ati = tti; + System.arraycopy(triB, 0, triA, 0, 9); + minXA = minXBuffer[ati]; + maxXA = maxXBuffer[ati]; + minYA = minYBuffer[ati]; + maxYA = maxYBuffer[ati]; + maxZA = maxZBuffer[ati]; + + testTid = activeTid + 1; + } else { + // oops, we already tested this one, either in one plane or + // interlocked in loop with others, just ignore it for now :( + testTid++; + } + } + } + } + activeTid++; + } + + { // Reorder the buffers + for (int id = 0; id < triangleCount; id++) { + int mappedId = triangleIndices[id]; + if (id != mappedId) { + + // put the first index aside + short i0 = polyIndices[3*id+0]; + short i1 = polyIndices[3*id+1]; + short i2 = polyIndices[3*id+2]; + int texId = texMap[id]; + int voffsetId = voffsetMap[id]; + + // process the whole permutation cycle + int currId = id; + int nextId = mappedId; + do { + triangleIndices[currId] = currId; + polyIndices[3*currId+0] = polyIndices[3*nextId+0]; + polyIndices[3*currId+1] = polyIndices[3*nextId+1]; + polyIndices[3*currId+2] = polyIndices[3*nextId+2]; + texMap[currId] = texMap[nextId]; + voffsetMap[currId] = voffsetMap[nextId]; + + currId = nextId; + nextId = triangleIndices[nextId]; + } while (nextId != id); + + // place the first index at the end + triangleIndices[currId] = currId; + polyIndices[3*currId+0] = i0; + polyIndices[3*currId+1] = i1; + polyIndices[3*currId+2] = i2; + texMap[currId] = texId; + voffsetMap[currId] = voffsetId; + } + } + } + + } + + static void fetchTriCoords(float[] tri, int ti, int[] vertexOffset, + int[] voffsetMap, float[] screenVertices, short[] polyIndices) { + int voffset = vertexOffset[voffsetMap[ti]]; + int i0 = 3 * (voffset + polyIndices[3*ti+0]); + int i1 = 3 * (voffset + polyIndices[3*ti+1]); + int i2 = 3 * (voffset + polyIndices[3*ti+2]); + tri[X0] = screenVertices[i0+X]; + tri[Y0] = screenVertices[i0+Y]; + tri[Z0] = screenVertices[i0+Z]; + tri[X1] = screenVertices[i1+X]; + tri[Y1] = screenVertices[i1+Y]; + tri[Z1] = screenVertices[i1+Z]; + tri[X2] = screenVertices[i2+X]; + tri[Y2] = screenVertices[i2+Y]; + tri[Z2] = screenVertices[i2+Z]; + } + + static void sortByMinZ(int leftTid, int rightTid, int[] triangleIndices, + float[] minZBuffer) { + + // swap pivot to the front + swap(triangleIndices, leftTid, ((leftTid + rightTid) / 2)); + + int k = leftTid; + float leftMinZ = minZBuffer[triangleIndices[leftTid]]; + + // sort by min z + for (int tid = leftTid+1; tid <= rightTid; tid++) { + float minZ = minZBuffer[triangleIndices[tid]]; + if (minZ < leftMinZ) { + swap(triangleIndices, ++k, tid); + } + } + + // swap pivot back to the middle + swap(triangleIndices, leftTid, k); + + if (leftTid < k - 1) sortByMinZ(leftTid, k - 1, triangleIndices, + minZBuffer); + if (k + 1 < rightTid) sortByMinZ(k + 1, rightTid, triangleIndices, + minZBuffer); + } + + // Math ----------------------------------------------- + + static int side(float[] tri1, float[] tri2, float tz) { + float Dx, Dy, Dz, Dw; + { // Get the equation of the plane + float + ABx = tri1[X1] - tri1[X0], ACx = tri1[X2] - tri1[X0], + ABy = tri1[Y1] - tri1[Y0], ACy = tri1[Y2] - tri1[Y0], + ABz = tri1[Z1] - tri1[Z0], ACz = tri1[Z2] - tri1[Z0]; + + Dx = ABy*ACz - ABz*ACy; Dy = ABz*ACx - ABx*ACz; Dz = ABx*ACy - ABy*ACx; + + // Normalize normal vector + float rMag = 1.0f/(float) Math.sqrt(Dx * Dx + Dy * Dy + Dz * Dz); + Dx *= rMag; Dy *= rMag; Dz *= rMag; + + Dw = -dot(Dx, Dy, Dz, tri1[X0], tri1[Y0], tri1[Z0]); + } + + float distTest = dot(Dx, Dy, Dz, + tri1[X0], tri1[Y0], tri1[Z0] + 100*tz) + Dw; + + float distA = dot(Dx, Dy, Dz, tri2[X0], tri2[Y0], tri2[Z0]) + Dw; + float distB = dot(Dx, Dy, Dz, tri2[X1], tri2[Y1], tri2[Z1]) + Dw; + float distC = dot(Dx, Dy, Dz, tri2[X2], tri2[Y2], tri2[Z2]) + Dw; + + // Ignore relatively close vertices to get stable results + // when some parts of polygons are close to each other + float absA = PApplet.abs(distA); + float absB = PApplet.abs(distB); + float absC = PApplet.abs(distC); + float eps = PApplet.max(absA, absB, absC) * 0.1f; + + float sideA = ((absA < eps) ? 0.0f : distA) * distTest; + float sideB = ((absB < eps) ? 0.0f : distB) * distTest; + float sideC = ((absC < eps) ? 0.0f : distC) * distTest; + + boolean sameSide = sideA >= 0 && sideB >= 0 && sideC >= 0; + boolean notSameSide = sideA <= 0 && sideB <= 0 && sideC <= 0; + + return sameSide ? 1 : notSameSide ? -1 : 0; + } + + static float dot(float a1, float a2, float a3, + float b1, float b2, float b3) { + return a1 * b1 + a2 * b2 + a3 * b3; } + + + // Array utils --------------------------------------- + + static void swap(int[] array, int i1, int i2) { + int temp = array[i1]; + array[i1] = array[i2]; + array[i2] = temp; + } + + static void rotateRight(int[] array, int i1, int i2) { + if (i1 == i2) return; + int temp = array[i2]; + System.arraycopy(array, i1, array, i1 + 1, i2 - i1); + array[i1] = temp; + } + } + } diff --git a/core/src/processing/opengl/PShader.java b/libs/processing-core/src/main/java/processing/opengl/PShader.java similarity index 93% rename from core/src/processing/opengl/PShader.java rename to libs/processing-core/src/main/java/processing/opengl/PShader.java index a6e37b84e..c3e47ead5 100644 --- a/core/src/processing/opengl/PShader.java +++ b/libs/processing-core/src/main/java/processing/opengl/PShader.java @@ -3,12 +3,13 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-13 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,6 +25,7 @@ package processing.opengl; import processing.core.*; +import processing.opengl.PGraphicsOpenGL.GLResourceShader; import java.net.URL; import java.nio.FloatBuffer; @@ -49,8 +51,12 @@ public class PShader implements PConstants { static protected String pointShaderAttrRegexp = "attribute *vec2 *offset"; + static protected String pointShaderInRegexp = + "in *vec2 *offset;"; static protected String lineShaderAttrRegexp = "attribute *vec4 *direction"; + static protected String lineShaderInRegexp = + "in *vec4 *direction"; static protected String pointShaderDefRegexp = "#define *PROCESSING_POINT_SHADER"; static protected String lineShaderDefRegexp = @@ -88,6 +94,7 @@ public class PShader implements PConstants { public int glProgram; public int glVertex; public int glFragment; + private GLResourceShader glres; protected URL vertexURL; protected URL fragmentURL; @@ -119,6 +126,7 @@ public class PShader implements PConstants { protected int ppixelsLoc; protected int ppixelsUnit; protected int viewportLoc; + protected int resolutionLoc; // Uniforms only for lines and points protected int perspectiveLoc; @@ -303,27 +311,9 @@ public PShader(PApplet parent, String[] vertSource, String[] fragSource) { } - @Override - protected void finalize() throws Throwable { - try { - if (glVertex != 0) { - PGraphicsOpenGL.finalizeGLSLVertShaderObject(glVertex, context); - } - if (glFragment != 0) { - PGraphicsOpenGL.finalizeGLSLFragShaderObject(glFragment, context); - } - if (glProgram != 0) { - PGraphicsOpenGL.finalizeGLSLProgramObject(glProgram, context); - } - } finally { - super.finalize(); - } - } - - public void setVertexShader(String vertFilename) { this.vertexFilename = vertFilename; - vertexShaderSource = pgl.loadFragmentShader(vertFilename); + vertexShaderSource = pgl.loadVertexShader(vertFilename); } @@ -340,13 +330,13 @@ public void setVertexShader(String[] vertSource) { public void setFragmentShader(String fragFilename) { this.fragmentFilename = fragFilename; - fragmentShaderSource = pgl.loadVertexShader(fragFilename); + fragmentShaderSource = pgl.loadFragmentShader(fragFilename); } public void setFragmentShader(URL fragURL) { this.fragmentURL = fragURL; - fragmentShaderSource = pgl.loadVertexShader(fragURL); + fragmentShaderSource = pgl.loadFragmentShader(fragURL); } public void setFragmentShader(String[] fragSource) { @@ -753,7 +743,8 @@ protected void setUniformImpl(String name, int type, Object value) { uniformValues.put(loc, new UniformValue(type, value)); } else { PGraphics.showWarning("The shader doesn't have a uniform called \"" + - name + "\""); + name + "\" OR the uniform was removed during " + + "compilation because it was unused."); } } @@ -896,61 +887,71 @@ protected void unbindTextures() { } } - protected void init() { - if (glProgram == 0 || contextIsOutdated()) { - context = pgl.getCurrentContext(); - glProgram = PGraphicsOpenGL.createGLSLProgramObject(context, pgl); - boolean vertRes = true; - if (hasVertexShader()) { - vertRes = compileVertexShader(); - } else { - PGraphics.showException("Doesn't have a vertex shader"); - } - - boolean fragRes = true; - if (hasFragmentShader()) { - fragRes = compileFragmentShader(); - } else { - PGraphics.showException("Doesn't have a fragment shader"); - } - - if (vertRes && fragRes) { + public void init() { + if (glProgram == 0 || contextIsOutdated()) { + create(); + if (compile()) { pgl.attachShader(glProgram, glVertex); pgl.attachShader(glProgram, glFragment); + setup(); pgl.linkProgram(glProgram); - pgl.getProgramiv(glProgram, PGL.LINK_STATUS, intBuffer); - boolean linked = intBuffer.get(0) == 0 ? false : true; - if (!linked) { - PGraphics.showException("Cannot link shader program:\n" + - pgl.getProgramInfoLog(glProgram)); - } - - pgl.validateProgram(glProgram); - pgl.getProgramiv(glProgram, PGL.VALIDATE_STATUS, intBuffer); - boolean validated = intBuffer.get(0) == 0 ? false : true; - if (!validated) { - PGraphics.showException("Cannot validate shader program:\n" + - pgl.getProgramInfoLog(glProgram)); - } + validate(); } } } + protected void create() { + context = pgl.getCurrentContext(); + glres = new GLResourceShader(this); + } + + + protected boolean compile() { + boolean vertRes = true; + if (hasVertexShader()) { + vertRes = compileVertexShader(); + } else { + PGraphics.showException("Doesn't have a vertex shader"); + } + + boolean fragRes = true; + if (hasFragmentShader()) { + fragRes = compileFragmentShader(); + } else { + PGraphics.showException("Doesn't have a fragment shader"); + } + + return vertRes && fragRes; + } + + + protected void validate() { + pgl.getProgramiv(glProgram, PGL.LINK_STATUS, intBuffer); + boolean linked = intBuffer.get(0) == 0 ? false : true; + if (!linked) { + PGraphics.showException("Cannot link shader program:\n" + + pgl.getProgramInfoLog(glProgram)); + } + + pgl.validateProgram(glProgram); + pgl.getProgramiv(glProgram, PGL.VALIDATE_STATUS, intBuffer); + boolean validated = intBuffer.get(0) == 0 ? false : true; + if (!validated) { + PGraphics.showException("Cannot validate shader program:\n" + + pgl.getProgramInfoLog(glProgram)); + } + } + + protected boolean contextIsOutdated() { boolean outdated = !pgl.contextIsCurrent(context); if (outdated) { - PGraphicsOpenGL.removeGLSLProgramObject(glProgram, context); - PGraphicsOpenGL.removeGLSLVertShaderObject(glVertex, context); - PGraphicsOpenGL.removeGLSLFragShaderObject(glFragment, context); - - glProgram = 0; - glVertex = 0; - glFragment = 0; + dispose(); } return outdated; } @@ -961,16 +962,16 @@ protected boolean hasVertexShader() { return vertexShaderSource != null && 0 < vertexShaderSource.length; } + protected boolean hasFragmentShader() { return fragmentShaderSource != null && 0 < fragmentShaderSource.length; } + /** * @param shaderSource a string containing the shader's code */ protected boolean compileVertexShader() { - glVertex = PGraphicsOpenGL.createGLSLVertShaderObject(context, pgl); - pgl.shaderSource(glVertex, PApplet.join(vertexShaderSource, "\n")); pgl.compileShader(glVertex); @@ -990,8 +991,6 @@ protected boolean compileVertexShader() { * @param shaderSource a string containing the shader's code */ protected boolean compileFragmentShader() { - glFragment = PGraphicsOpenGL.createGLSLFragShaderObject(context, pgl); - pgl.shaderSource(glFragment, PApplet.join(fragmentShaderSource, "\n")); pgl.compileShader(glFragment); @@ -1008,32 +1007,21 @@ protected boolean compileFragmentShader() { protected void dispose() { - if (glVertex != 0) { - PGraphicsOpenGL.deleteGLSLVertShaderObject(glVertex, context, pgl); + if (glres != null) { + glres.dispose(); glVertex = 0; - } - if (glFragment != 0) { - PGraphicsOpenGL.deleteGLSLFragShaderObject(glFragment, context, pgl); glFragment = 0; - } - if (glProgram != 0) { - PGraphicsOpenGL.deleteGLSLProgramObject(glProgram, context, pgl); glProgram = 0; + glres = null; } } + static protected int getShaderType(String[] source, int defaultType) { for (int i = 0; i < source.length; i++) { String line = source[i].trim(); - if (PApplet.match(line, pointShaderAttrRegexp) != null) - return PShader.POINT; - else if (PApplet.match(line, lineShaderAttrRegexp) != null) - return PShader.LINE; - else if (PApplet.match(line, pointShaderDefRegexp) != null) - return PShader.POINT; - else if (PApplet.match(line, lineShaderDefRegexp) != null) - return PShader.LINE; - else if (PApplet.match(line, colorShaderDefRegexp) != null) + + if (PApplet.match(line, colorShaderDefRegexp) != null) return PShader.COLOR; else if (PApplet.match(line, lightShaderDefRegexp) != null) return PShader.LIGHT; @@ -1047,6 +1035,18 @@ else if (PApplet.match(line, triShaderAttrRegexp) != null) return PShader.POLY; else if (PApplet.match(line, quadShaderAttrRegexp) != null) return PShader.POLY; + else if (PApplet.match(line, pointShaderDefRegexp) != null) + return PShader.POINT; + else if (PApplet.match(line, lineShaderDefRegexp) != null) + return PShader.LINE; + else if (PApplet.match(line, pointShaderAttrRegexp) != null) + return PShader.POINT; + else if (PApplet.match(line, pointShaderInRegexp) != null) + return PShader.POINT; + else if (PApplet.match(line, lineShaderAttrRegexp) != null) + return PShader.LINE; + else if (PApplet.match(line, lineShaderInRegexp) != null) + return PShader.LINE; } return defaultType; } @@ -1158,6 +1158,7 @@ protected void loadUniforms() { projectionMatLoc = getUniformLoc("projectionMatrix"); viewportLoc = getUniformLoc("viewport"); + resolutionLoc = getUniformLoc("resolution"); ppixelsLoc = getUniformLoc("ppixels"); normalMatLoc = getUniformLoc("normalMatrix"); @@ -1209,6 +1210,12 @@ protected void setCommonUniforms() { setUniformValue(viewportLoc, x, y, w, h); } + if (-1 < resolutionLoc) { + float w = currentPG.viewport.get(2); + float h = currentPG.viewport.get(3); + setUniformValue(resolutionLoc, w, h); + } + if (-1 < ppixelsLoc) { ppixelsUnit = getLastTexUnit() + 1; setUniformValue(ppixelsLoc, ppixelsUnit); @@ -1219,6 +1226,7 @@ protected void setCommonUniforms() { } } + protected void bindTyped() { if (currentPG == null) { setRenderer(primaryPG.getCurrentPG()); @@ -1305,7 +1313,7 @@ protected void unbindTyped() { if (-1 < normalLoc) pgl.disableVertexAttribArray(normalLoc); if (-1 < ppixelsLoc) { - pgl.requestFBOLayer(); + pgl.enableFBOLayer(); pgl.activeTexture(PGL.TEXTURE0 + ppixelsUnit); currentPG.unbindFrontTexture(); pgl.activeTexture(PGL.TEXTURE0); @@ -1437,7 +1445,7 @@ protected void setPointAttribute(int vboId, int size, int type, // // Class to store a user-specified value for a uniform parameter // in the shader - protected class UniformValue { + protected static class UniformValue { static final int INT1 = 0; static final int INT2 = 1; static final int INT3 = 2; diff --git a/core/src/processing/opengl/PShapeOpenGL.java b/libs/processing-core/src/main/java/processing/opengl/PShapeOpenGL.java similarity index 77% rename from core/src/processing/opengl/PShapeOpenGL.java rename to libs/processing-core/src/main/java/processing/opengl/PShapeOpenGL.java index a771ff053..3f65727b4 100644 --- a/core/src/processing/opengl/PShapeOpenGL.java +++ b/libs/processing-core/src/main/java/processing/opengl/PShapeOpenGL.java @@ -3,7 +3,9 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2011-12 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -31,14 +33,16 @@ import processing.core.PMatrix3D; import processing.core.PShape; import processing.core.PVector; +import processing.opengl.PGraphicsOpenGL.AttributeMap; import processing.opengl.PGraphicsOpenGL.IndexCache; import processing.opengl.PGraphicsOpenGL.InGeometry; import processing.opengl.PGraphicsOpenGL.TessGeometry; import processing.opengl.PGraphicsOpenGL.Tessellator; +import processing.opengl.PGraphicsOpenGL.VertexAttribute; +import java.nio.Buffer; import java.util.Arrays; import java.util.HashSet; -import java.util.Stack; /** * This class holds a 3D model composed of vertices, normals, colors @@ -84,36 +88,39 @@ public class PShapeOpenGL extends PShape { protected TessGeometry tessGeo; protected Tessellator tessellator; + protected AttributeMap polyAttribs; + // ........................................................ // Texturing protected HashSet textures; protected boolean strokedTexture; + protected boolean untexChild; // ........................................................ // OpenGL buffers - public int glPolyVertex; - public int glPolyColor; - public int glPolyNormal; - public int glPolyTexcoord; - public int glPolyAmbient; - public int glPolySpecular; - public int glPolyEmissive; - public int glPolyShininess; - public int glPolyIndex; - - public int glLineVertex; - public int glLineColor; - public int glLineAttrib; - public int glLineIndex; - - public int glPointVertex; - public int glPointColor; - public int glPointAttrib; - public int glPointIndex; + protected VertexBuffer bufPolyVertex; + protected VertexBuffer bufPolyColor; + protected VertexBuffer bufPolyNormal; + protected VertexBuffer bufPolyTexcoord; + protected VertexBuffer bufPolyAmbient; + protected VertexBuffer bufPolySpecular; + protected VertexBuffer bufPolyEmissive; + protected VertexBuffer bufPolyShininess; + protected VertexBuffer bufPolyIndex; + + protected VertexBuffer bufLineVertex; + protected VertexBuffer bufLineColor; + protected VertexBuffer bufLineAttrib; + protected VertexBuffer bufLineIndex; + + protected VertexBuffer bufPointVertex; + protected VertexBuffer bufPointColor; + protected VertexBuffer bufPointAttrib; + protected VertexBuffer bufPointIndex; // Testing this field, not use as it might go away... public int glUsage = PGL.STATIC_DRAW; @@ -163,7 +170,8 @@ public class PShapeOpenGL extends PShape { // Geometric transformations. protected PMatrix transform; - protected Stack transformStack; + protected PMatrix transformInv; + protected PMatrix matrixInv; // ........................................................ @@ -173,7 +181,7 @@ public class PShapeOpenGL extends PShape { protected boolean needBufferInit = false; // Flag to indicate if the shape can have holes or not. - protected boolean solid; + protected boolean solid = true; protected boolean breakShape = false; protected boolean shapeCreated = false; @@ -302,44 +310,46 @@ public class PShapeOpenGL extends PShape { public PShapeOpenGL(PGraphicsOpenGL pg, int family) { this.pg = pg; + this.family = family; + pgl = pg.pgl; context = pgl.createEmptyContext(); - glPolyVertex = 0; - glPolyColor = 0; - glPolyNormal = 0; - glPolyTexcoord = 0; - glPolyAmbient = 0; - glPolySpecular = 0; - glPolyEmissive = 0; - glPolyShininess = 0; - glPolyIndex = 0; - - glLineVertex = 0; - glLineColor = 0; - glLineAttrib = 0; - glLineIndex = 0; - - glPointVertex = 0; - glPointColor = 0; - glPointAttrib = 0; - glPointIndex = 0; - - this.tessellator = PGraphicsOpenGL.tessellator; - this.family = family; + bufPolyVertex = null; + bufPolyColor = null; + bufPolyNormal = null; + bufPolyTexcoord = null; + bufPolyAmbient = null; + bufPolySpecular = null; + bufPolyEmissive = null; + bufPolyShininess = null; + bufPolyIndex = null; + + bufLineVertex = null; + bufLineColor = null; + bufLineAttrib = null; + bufLineIndex = null; + + bufPointVertex = null; + bufPointColor = null; + bufPointAttrib = null; + bufPointIndex = null; + + this.tessellator = pg.tessellator; this.root = this; this.parent = null; this.tessellated = false; if (family == GEOMETRY || family == PRIMITIVE || family == PATH) { - inGeo = PGraphicsOpenGL.newInGeometry(pg, PGraphicsOpenGL.RETAINED); + polyAttribs = PGraphicsOpenGL.newAttributeMap(); + inGeo = PGraphicsOpenGL.newInGeometry(pg, polyAttribs, PGraphicsOpenGL.RETAINED); } // Style parameters are retrieved from the current values in the renderer. textureMode = pg.textureMode; colorMode(pg.colorMode, - pg.colorModeX, pg.colorModeY, pg.colorModeZ, pg.colorModeA); + pg.colorModeX, pg.colorModeY, pg.colorModeZ, pg.colorModeA); // Initial values for fill, stroke and tint colors are also imported from // the renderer. This is particular relevant for primitive shapes, since is @@ -371,10 +381,8 @@ public PShapeOpenGL(PGraphicsOpenGL pg, int family) { curveDetail = pg.curveDetail; curveTightness = pg.curveTightness; - // The rect and ellipse modes are set to CORNER since it is the expected - // mode for svg shapes. - rectMode = CORNER; - ellipseMode = CORNER; + rectMode = pg.rectMode; + ellipseMode = pg.ellipseMode; normalX = normalY = 0; normalZ = 1; @@ -389,6 +397,17 @@ public PShapeOpenGL(PGraphicsOpenGL pg, int family) { // GROUP shapes are always marked as ended. shapeCreated = true; } + + // OpenGL supports per-vertex coloring (unlike Java2D) + perVertexStyles = true; + } + + + /** Create a shape from the PRIMITIVE family, using this kind and these params */ + public PShapeOpenGL(PGraphicsOpenGL pg, int kind, float... p) { + this(pg, PRIMITIVE); + setKind(kind); + setParams(p); } @@ -407,6 +426,8 @@ public void addChild(PShape who) { for (PImage tex: c3d.textures) { addTexture(tex); } + } else { + untexChild(true); } if (c3d.strokedTexture) { strokedTexture(true); @@ -417,6 +438,8 @@ public void addChild(PShape who) { if (c3d.stroke) { strokedTexture(true); } + } else { + untexChild(true); } } @@ -444,6 +467,8 @@ public void addChild(PShape who, int idx) { for (PImage tex: c3d.textures) { addTexture(tex); } + } else { + untexChild(true); } if (c3d.strokedTexture) { strokedTexture(true); @@ -454,6 +479,8 @@ public void addChild(PShape who, int idx) { if (c3d.stroke) { strokedTexture(true); } + } else { + untexChild(true); } } @@ -469,6 +496,8 @@ public void addChild(PShape who, int idx) { @Override public void removeChild(int idx) { super.removeChild(idx); + strokedTexture(false); + untexChild(false); markForTessellation(); } @@ -484,113 +513,29 @@ protected void updateRoot(PShape root) { } - @Override - protected void finalize() throws Throwable { - try { - finalizePolyBuffers(); - finalizeLineBuffers(); - finalizePointBuffers(); - } finally { - super.finalize(); - } - } - - - protected void finalizePolyBuffers() { - if (glPolyVertex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyVertex, context); - } - - if (glPolyColor != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyColor, context); - } - - if (glPolyNormal != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyNormal, context); - } - - if (glPolyTexcoord != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyTexcoord, context); - } - - if (glPolyAmbient != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyAmbient, context); - } - - if (glPolySpecular != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolySpecular, context); - } - - if (glPolyEmissive != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyEmissive, context); - } - - if (glPolyShininess != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyShininess, context); - } - - if (glPolyIndex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPolyIndex, context); - } - } - - - protected void finalizeLineBuffers() { - if (glLineVertex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineVertex, context); - } - - if (glLineColor != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineColor, context); - } - - if (glLineAttrib != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineAttrib, context); - } - - if (glLineIndex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glLineIndex, context); - } - } - - - protected void finalizePointBuffers() { - if (glPointVertex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointVertex, context); - } - - if (glPointColor != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointColor, context); - } - - if (glPointAttrib != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointAttrib, context); - } - - if (glPointIndex != 0) { - PGraphicsOpenGL.finalizeVertexBufferObject(glPointIndex, context); - } - } - /////////////////////////////////////////////////////////// // // Shape creation (temporary hack) - public static PShapeOpenGL createShape3D(PGraphicsOpenGL pg, PShape src) { + public static PShapeOpenGL createShape(PGraphicsOpenGL pg, PShape src) { PShapeOpenGL dest = null; if (src.getFamily() == GROUP) { - dest = PGraphics3D.createShapeImpl(pg, GROUP); - copyGroup3D(pg, src, dest); + //dest = PGraphics3D.createShapeImpl(pg, GROUP); + dest = (PShapeOpenGL) pg.createShapeFamily(GROUP); + copyGroup(pg, src, dest); } else if (src.getFamily() == PRIMITIVE) { - dest = PGraphics3D.createShapeImpl(pg, src.getKind(), src.getParams()); + //dest = PGraphics3D.createShapeImpl(pg, src.getKind(), src.getParams()); + dest = (PShapeOpenGL) pg.createShapePrimitive(src.getKind(), src.getParams()); PShape.copyPrimitive(src, dest); } else if (src.getFamily() == GEOMETRY) { - dest = PGraphics3D.createShapeImpl(pg, PShape.GEOMETRY); + //dest = PGraphics3D.createShapeImpl(pg, PShape.GEOMETRY); + dest = (PShapeOpenGL) pg.createShapeFamily(PShape.GEOMETRY); PShape.copyGeometry(src, dest); } else if (src.getFamily() == PATH) { - dest = PGraphics3D.createShapeImpl(pg, PATH); + dest = (PShapeOpenGL) pg.createShapeFamily(PShape.PATH); + //dest = PGraphics3D.createShapeImpl(pg, PATH); PShape.copyPath(src, dest); } dest.setName(src.getName()); @@ -601,19 +546,24 @@ public static PShapeOpenGL createShape3D(PGraphicsOpenGL pg, PShape src) { } + /* static public PShapeOpenGL createShape2D(PGraphicsOpenGL pg, PShape src) { PShapeOpenGL dest = null; if (src.getFamily() == GROUP) { - dest = PGraphics2D.createShapeImpl(pg, GROUP); + //dest = PGraphics2D.createShapeImpl(pg, GROUP); + dest = (PShapeOpenGL) pg.createShapeFamily(GROUP); copyGroup2D(pg, src, dest); } else if (src.getFamily() == PRIMITIVE) { - dest = PGraphics2D.createShapeImpl(pg, src.getKind(), src.getParams()); + //dest = PGraphics2D.createShapeImpl(pg, src.getKind(), src.getParams()); + dest = (PShapeOpenGL) pg.createShapePrimitive(src.getKind(), src.getParams()); PShape.copyPrimitive(src, dest); } else if (src.getFamily() == GEOMETRY) { - dest = PGraphics2D.createShapeImpl(pg, PShape.GEOMETRY); + //dest = PGraphics2D.createShapeImpl(pg, PShape.GEOMETRY); + dest = (PShapeOpenGL) pg.createShapeFamily(PShape.GEOMETRY); PShape.copyGeometry(src, dest); } else if (src.getFamily() == PATH) { - dest = PGraphics2D.createShapeImpl(pg, PATH); + //dest = PGraphics2D.createShapeImpl(pg, PATH); + dest = (PShapeOpenGL) pg.createShapeFamily(PShape.PATH); PShape.copyPath(src, dest); } dest.setName(src.getName()); @@ -621,20 +571,21 @@ static public PShapeOpenGL createShape2D(PGraphicsOpenGL pg, PShape src) { dest.height = src.height; return dest; } +*/ - - static public void copyGroup3D(PGraphicsOpenGL pg, PShape src, PShape dest) { + static public void copyGroup(PGraphicsOpenGL pg, PShape src, PShape dest) { copyMatrix(src, dest); copyStyles(src, dest); copyImage(src, dest); for (int i = 0; i < src.getChildCount(); i++) { - PShape c = createShape3D(pg, src.getChild(i)); + PShape c = createShape(pg, src.getChild(i)); dest.addChild(c); } } + /* static public void copyGroup2D(PGraphicsOpenGL pg, PShape src, PShape dest) { copyMatrix(src, dest); copyStyles(src, dest); @@ -645,7 +596,7 @@ static public void copyGroup2D(PGraphicsOpenGL pg, PShape src, PShape dest) { dest.addChild(c); } } - +*/ /////////////////////////////////////////////////////////// @@ -657,9 +608,9 @@ static public void copyGroup2D(PGraphicsOpenGL pg, PShape src, PShape dest) { @Override public float getWidth() { PVector min = new PVector(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY); + Float.POSITIVE_INFINITY); PVector max = new PVector(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, - Float.NEGATIVE_INFINITY); + Float.NEGATIVE_INFINITY); if (shapeCreated) { getVertexMin(min); getVertexMax(max); @@ -672,9 +623,9 @@ public float getWidth() { @Override public float getHeight() { PVector min = new PVector(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY); + Float.POSITIVE_INFINITY); PVector max = new PVector(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, - Float.NEGATIVE_INFINITY); + Float.NEGATIVE_INFINITY); if (shapeCreated) { getVertexMin(min); getVertexMax(max); @@ -687,9 +638,9 @@ public float getHeight() { @Override public float getDepth() { PVector min = new PVector(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, - Float.POSITIVE_INFINITY); + Float.POSITIVE_INFINITY); PVector max = new PVector(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, - Float.NEGATIVE_INFINITY); + Float.NEGATIVE_INFINITY); if (shapeCreated) { getVertexMin(min); getVertexMax(max); @@ -762,11 +713,11 @@ protected int getVertexSum(PVector sum, int count) { if (is3D()) { if (hasLines) { count += tessGeo.getLineVertexSum(sum, firstLineVertex, - lastLineVertex); + lastLineVertex); } if (hasPoints) { count += tessGeo.getPointVertexSum(sum, firstPointVertex, - lastPointVertex); + lastPointVertex); } } } @@ -852,7 +803,7 @@ protected void setTextureImpl(PImage tex) { } if (image0 != tex && parent != null) { - ((PShapeOpenGL)parent).removeTexture(tex); + ((PShapeOpenGL)parent).removeTexture(image0, this); } if (parent != null) { ((PShapeOpenGL)parent).addTexture(image); @@ -896,7 +847,7 @@ protected void scaleTextureUV(float uFactor, float vFactor) { protected void addTexture(PImage tex) { if (textures == null) { - textures = new HashSet(); + textures = new HashSet<>(); } textures.add(tex); if (parent != null) { @@ -905,14 +856,14 @@ protected void addTexture(PImage tex) { } - protected void removeTexture(PImage tex) { + protected void removeTexture(PImage tex, PShapeOpenGL caller) { if (textures == null || !textures.contains(tex)) return; // Nothing to remove. - // First check that none of the child shapes - // have texture tex... + // First check that none of the child shapes have texture tex... boolean childHasTex = false; for (int i = 0; i < childCount; i++) { PShapeOpenGL child = (PShapeOpenGL) children[i]; + if (child == caller) continue; if (child.hasTexture(tex)) { childHasTex = true; break; @@ -930,38 +881,76 @@ protected void removeTexture(PImage tex) { // Since this shape and all its child shapes don't contain // tex anymore, we now can remove it from the parent. if (parent != null) { - ((PShapeOpenGL)parent).removeTexture(tex); + ((PShapeOpenGL)parent).removeTexture(tex, this); } } protected void strokedTexture(boolean newValue) { + strokedTexture(newValue, null); + } + + + protected void strokedTexture(boolean newValue, PShapeOpenGL caller) { if (strokedTexture == newValue) return; // Nothing to change. if (newValue) { strokedTexture = true; } else { - // First check that none of the child shapes - // have have a stroked texture... - boolean childHasStrokedTex = false; + // Check that none of the child shapes have a stroked texture... + strokedTexture = false; for (int i = 0; i < childCount; i++) { PShapeOpenGL child = (PShapeOpenGL) children[i]; + if (child == caller) continue; if (child.hasStrokedTexture()) { - childHasStrokedTex = true; + strokedTexture = true; break; } } + } + + // Now we can update the parent shape. + if (parent != null) { + ((PShapeOpenGL)parent).strokedTexture(newValue, this); + } + } - if (!childHasStrokedTex) { - // ...if not, it is safe to mark this shape as without - // stroked texture. - strokedTexture = false; + + protected void untexChild(boolean newValue) { + untexChild(newValue, null); + } + + + protected void untexChild(boolean newValue, PShapeOpenGL caller) { + if (untexChild == newValue) return; // Nothing to change. + + if (newValue) { + untexChild = true; + } else { + // Check if any of the child shapes is not textured... + untexChild = false; + for (int i = 0; i < childCount; i++) { + PShapeOpenGL child = (PShapeOpenGL) children[i]; + if (child == caller) continue; + if (!child.hasTexture()) { + untexChild = true; + break; + } } } // Now we can update the parent shape. if (parent != null) { - ((PShapeOpenGL)parent).strokedTexture(newValue); + ((PShapeOpenGL)parent).untexChild(newValue, this); + } + } + + + protected boolean hasTexture() { + if (family == GROUP) { + return textures != null && 0 < textures.size(); + } else { + return image != null; } } @@ -1074,12 +1063,12 @@ protected void vertexImpl(float x, float y, float z, float u, float v) { } inGeo.addVertex(x, y, z, - fcolor, - normalX, normalY, normalZ, - u, v, - scolor, sweight, - ambientColor, specularColor, emissiveColor, shininess, - VERTEX, vertexBreak()); + fcolor, + normalX, normalY, normalZ, + u, v, + scolor, sweight, + ambientColor, specularColor, emissiveColor, shininess, + VERTEX, vertexBreak()); markForTessellation(); } @@ -1122,6 +1111,80 @@ public void normal(float nx, float ny, float nz) { } + @Override + public void attribPosition(String name, float x, float y, float z) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.POSITION, + PGL.FLOAT, 3); + if (attrib != null) attrib.set(x, y, z); + } + + + @Override + public void attribNormal(String name, float nx, float ny, float nz) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.NORMAL, + PGL.FLOAT, 3); + if (attrib != null) attrib.set(nx, ny, nz); + } + + + @Override + public void attribColor(String name, int color) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.COLOR, PGL.INT, 1); + if (attrib != null) attrib.set(new int[] {color}); + } + + + @Override + public void attrib(String name, float... values) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.FLOAT, + values.length); + if (attrib != null) attrib.set(values); + } + + + @Override + public void attrib(String name, int... values) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.INT, + values.length); + if (attrib != null) attrib.set(values); + } + + + @Override + public void attrib(String name, boolean... values) { + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.BOOL, + values.length); + if (attrib != null) attrib.set(values); + } + + + protected VertexAttribute attribImpl(String name, int kind, int type, int size) { + if (4 < size) { + PGraphics.showWarning("Vertex attributes cannot have more than 4 values"); + return null; + } + VertexAttribute attrib = polyAttribs.get(name); + if (attrib == null) { + attrib = new VertexAttribute(pg, name, kind, type, size); + polyAttribs.put(name, attrib); + inGeo.initAttrib(attrib); + } + if (attrib.kind != kind) { + PGraphics.showWarning("The attribute kind cannot be changed after creation"); + return null; + } + if (attrib.type != type) { + PGraphics.showWarning("The attribute type cannot be changed after creation"); + return null; + } + if (attrib.size != size) { + PGraphics.showWarning("New value for vertex attribute has wrong number of values"); + return null; + } + return attrib; + } + + @Override public void endShape(int mode) { super.endShape(mode); @@ -1153,7 +1216,7 @@ public void setParams(float[] source) { public void setPath(int vcount, float[][] verts, int ccount, int[] codes) { if (family != PATH) { PGraphics.showWarning("Vertex coordinates and codes can only be set to " + - "PATH shapes"); + "PATH shapes"); return; } @@ -1188,7 +1251,11 @@ public void translate(float tx, float ty, float tz) { @Override public void rotate(float angle) { - transform(ROTATE, angle); + if (is3D) { + transform(ROTATE, angle, 0, 0, 1); + } else { + transform(ROTATE, angle); + } } @@ -1245,7 +1312,7 @@ public void scale(float x, float y, float z) { @Override public void applyMatrix(PMatrix2D source) { transform(MATRIX, source.m00, source.m01, source.m02, - source.m10, source.m11, source.m12); + source.m10, source.m11, source.m12); } @@ -1253,7 +1320,7 @@ public void applyMatrix(PMatrix2D source) { public void applyMatrix(float n00, float n01, float n02, float n10, float n11, float n12) { transform(MATRIX, n00, n01, n02, - n10, n11, n12); + n10, n11, n12); } @@ -1263,47 +1330,42 @@ public void applyMatrix(float n00, float n01, float n02, float n03, float n20, float n21, float n22, float n23, float n30, float n31, float n32, float n33) { transform(MATRIX, n00, n01, n02, n03, - n10, n11, n12, n13, - n20, n21, n22, n23, - n30, n31, n32, n33); + n10, n11, n12, n13, + n20, n21, n22, n23, + n30, n31, n32, n33); } @Override public void resetMatrix() { - if (shapeCreated && matrix != null && transformStack != null) { + if (shapeCreated && matrix != null && matrixInv != null) { if (family == GROUP) { updateTessellation(); } if (tessellated) { - PMatrix mat = popTransform(); - while (mat != null) { - boolean res = mat.invert(); - if (res) { - applyMatrixImpl(mat); - } else { - PGraphics.showWarning("Transformation applied on the shape cannot be inverted"); - } - mat = popTransform(); - } + applyMatrixImpl(matrixInv); } matrix.reset(); - transformStack.clear(); + matrixInv.reset(); } } protected void transform(int type, float... args) { int dimensions = is3D ? 3 : 2; + boolean invertible = true; checkMatrix(dimensions); if (transform == null) { if (dimensions == 2) { transform = new PMatrix2D(); + transformInv = new PMatrix2D(); } else { transform = new PMatrix3D(); + transformInv = new PMatrix3D(); } } else { transform.reset(); + transformInv.reset(); } int ncoords = args.length; @@ -1314,88 +1376,105 @@ protected void transform(int type, float... args) { } switch (type) { - case TRANSLATE: - if (ncoords == 3) { - transform.translate(args[0], args[1], args[2]); - } else { - transform.translate(args[0], args[1]); - } - break; - case ROTATE: - if (ncoords == 3) { - transform.rotate(args[0], args[1], args[2], args[3]); - } else { - transform.rotate(args[0]); - } - break; - case SCALE: - if (ncoords == 3) { - transform.scale(args[0], args[1], args[2]); - } else { - transform.scale(args[0], args[1]); - } - break; - case MATRIX: - if (ncoords == 3) { - transform.set(args[ 0], args[ 1], args[ 2], args[ 3], - args[ 4], args[ 5], args[ 6], args[ 7], - args[ 8], args[ 9], args[10], args[11], - args[12], args[13], args[14], args[15]); - } else { - transform.set(args[0], args[1], args[2], - args[3], args[4], args[5]); - } - break; + case TRANSLATE: + if (ncoords == 3) { + transform.translate(args[0], args[1], args[2]); + PGraphicsOpenGL.invTranslate((PMatrix3D)transformInv, args[0], args[1], args[2]); + } else { + transform.translate(args[0], args[1]); + PGraphicsOpenGL.invTranslate((PMatrix2D)transformInv, args[0], args[1]); + } + break; + case ROTATE: + if (ncoords == 3) { + transform.rotate(args[0], args[1], args[2], args[3]); + PGraphicsOpenGL.invRotate((PMatrix3D)transformInv, args[0], args[1], args[2], args[3]); + } else { + transform.rotate(args[0]); + PGraphicsOpenGL.invRotate((PMatrix2D)transformInv, -args[0]); + } + break; + case SCALE: + if (ncoords == 3) { + transform.scale(args[0], args[1], args[2]); + PGraphicsOpenGL.invScale((PMatrix3D)transformInv, args[0], args[1], args[2]); + } else { + transform.scale(args[0], args[1]); + PGraphicsOpenGL.invScale((PMatrix2D)transformInv, args[0], args[1]); + } + break; + case MATRIX: + if (ncoords == 3) { + transform.set(args[ 0], args[ 1], args[ 2], args[ 3], + args[ 4], args[ 5], args[ 6], args[ 7], + args[ 8], args[ 9], args[10], args[11], + args[12], args[13], args[14], args[15]); + } else { + transform.set(args[0], args[1], args[2], + args[3], args[4], args[5]); + } + transformInv.set(transform); + invertible = transformInv.invert(); + break; } - matrix.apply(transform); - pushTransform(); - if (tessellated) applyMatrixImpl(transform); - } - - - protected void pushTransform() { - if (transformStack == null) transformStack = new Stack(); - PMatrix mat; - if (transform instanceof PMatrix2D) { - mat = new PMatrix2D(); + matrix.preApply(transform); + if (invertible) { + matrixInv.apply(transformInv); } else { - mat = new PMatrix3D(); + PGraphics.showWarning("Transformation applied on the shape cannot be inverted"); } - mat.set(transform); - transformStack.push(mat); + if (tessellated) applyMatrixImpl(transform); } - protected PMatrix popTransform() { - if (transformStack == null || transformStack.size() == 0) return null; - return transformStack.pop(); - } - protected void applyMatrixImpl(PMatrix matrix) { if (hasPolys) { tessGeo.applyMatrixOnPolyGeometry(matrix, - firstPolyVertex, lastPolyVertex); + firstPolyVertex, lastPolyVertex); root.setModifiedPolyVertices(firstPolyVertex, lastPolyVertex); root.setModifiedPolyNormals(firstPolyVertex, lastPolyVertex); + for (VertexAttribute attrib: polyAttribs.values()) { + if (attrib.isPosition() || attrib.isNormal()) { + root.setModifiedPolyAttrib(attrib, firstPolyVertex, lastPolyVertex); + } + } } if (is3D()) { if (hasLines) { tessGeo.applyMatrixOnLineGeometry(matrix, - firstLineVertex, lastLineVertex); + firstLineVertex, lastLineVertex); root.setModifiedLineVertices(firstLineVertex, lastLineVertex); root.setModifiedLineAttributes(firstLineVertex, lastLineVertex); } if (hasPoints) { tessGeo.applyMatrixOnPointGeometry(matrix, - firstPointVertex, lastPointVertex); + firstPointVertex, lastPointVertex); root.setModifiedPointVertices(firstPointVertex, lastPointVertex); + root.setModifiedPointAttributes(firstPointVertex, lastPointVertex); } } } + @Override + protected void checkMatrix(int dimensions) { + if (matrix == null) { + if (dimensions == 2) { + matrix = new PMatrix2D(); + matrixInv = new PMatrix2D(); + } else { + matrix = new PMatrix3D(); + matrixInv = new PMatrix3D(); + } + } else if (dimensions == 3 && (matrix instanceof PMatrix2D)) { + matrix = new PMatrix3D(matrix); + matrixInv = new PMatrix3D(matrixInv); + } + } + + /////////////////////////////////////////////////////////// // @@ -1418,8 +1497,8 @@ public void bezierVertex(float x2, float y2, float x3, float y3, float x4, float y4) { bezierVertexImpl(x2, y2, 0, - x3, y3, 0, - x4, y4, 0); + x3, y3, 0, + x4, y4, 0); } @@ -1428,8 +1507,8 @@ public void bezierVertex(float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4) { bezierVertexImpl(x2, y2, z2, - x3, y3, z3, - x4, y4, z4); + x3, y3, z3, + x4, y4, z4); } @@ -1437,19 +1516,11 @@ protected void bezierVertexImpl(float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4) { inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addBezierVertex(x2, y2, z2, - x3, y3, z3, - x4, y4, z4, vertexBreak()); - -// inGeo.addVertex(x2, y2, z2, BEZIER_VERTEX, vertexBreak()); -// inGeo.addVertex(x3, y3, z3, BEZIER_VERTEX, false); -// inGeo.addVertex(x4, y4, z4, BEZIER_VERTEX, false); -//// inGeo.addBezierVertex(x2, y2, z2, -// x3, y3, z3, -// x4, y4, z4, -// fill, stroke, bezierDetail, vertexCode(), kind); + x3, y3, z3, + x4, y4, z4, vertexBreak()); } @@ -1457,7 +1528,7 @@ protected void bezierVertexImpl(float x2, float y2, float z2, public void quadraticVertex(float cx, float cy, float x3, float y3) { quadraticVertexImpl(cx, cy, 0, - x3, y3, 0); + x3, y3, 0); } @@ -1465,22 +1536,17 @@ public void quadraticVertex(float cx, float cy, public void quadraticVertex(float cx, float cy, float cz, float x3, float y3, float z3) { quadraticVertexImpl(cx, cy, cz, - x3, y3, z3); + x3, y3, z3); } protected void quadraticVertexImpl(float cx, float cy, float cz, float x3, float y3, float z3) { inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addQuadraticVertex(cx, cy, cz, - x3, y3, z3, vertexBreak()); -// inGeo.addVertex(cx, cy, cz, QUADRATIC_VERTEX, vertexBreak()); -// inGeo.addVertex(x3, y3, z3, QUADRATIC_VERTEX, false); -// inGeo.addQuadraticVertex(cx, cy, cz, -// x3, y3, z3, -// fill, stroke, bezierDetail, vertexCode(), kind); + x3, y3, z3, vertexBreak()); } @@ -1525,12 +1591,9 @@ public void curveVertex(float x, float y, float z) { protected void curveVertexImpl(float x, float y, float z) { inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addCurveVertex(x, y, z, vertexBreak()); -// inGeo.addVertex(x, y, z, CURVE_VERTEX, vertexBreak()); -// inGeo.addCurveVertex(x, y, z, -// fill, stroke, curveDetail, vertexCode(), kind); } @@ -1600,10 +1663,26 @@ public void setVertex(int index, float x, float y, float z) { // TODO: in certain cases (kind = TRIANGLE, etc) the correspondence between // input and tessellated vertices is 1-1, so in those cases re-tessellation - // wouldnt' be neccessary. - inGeo.vertices[3 * index + 0] = x; - inGeo.vertices[3 * index + 1] = y; - inGeo.vertices[3 * index + 2] = z; + // wouldn't be necessary. But in order to reasonable take care of that + // situation, we would need a complete rethinking of the rendering architecture + // in Processing :-) + if (family == PATH) { + if (vertexCodes != null && vertexCodeCount > 0 && + vertexCodes[index] != VERTEX) { + PGraphics.showWarning(NOT_A_SIMPLE_VERTEX, "setVertex()"); + return; + } + vertices[index][X] = x; + vertices[index][Y] = y; + if (is3D && vertices[index].length > 2) { + // P3D allows to modify 2D shapes, ignoring the Z coordinate. + vertices[index][Z] = z; + } + } else { + inGeo.vertices[3 * index + 0] = x; + inGeo.vertices[3 * index + 1] = y; + inGeo.vertices[3 * index + 2] = z; + } markForTessellation(); } @@ -1615,9 +1694,22 @@ public void setVertex(int index, PVector vec) { return; } - inGeo.vertices[3 * index + 0] = vec.x; - inGeo.vertices[3 * index + 1] = vec.y; - inGeo.vertices[3 * index + 2] = vec.z; + if (family == PATH) { + if (vertexCodes != null && vertexCodeCount > 0 && + vertexCodes[index] != VERTEX) { + PGraphics.showWarning(NOT_A_SIMPLE_VERTEX, "setVertex()"); + return; + } + vertices[index][X] = vec.x; + vertices[index][Y] = vec.y; + if (is3D && vertices[index].length > 2) { + vertices[index][Z] = vec.z; + } + } else { + inGeo.vertices[3 * index + 0] = vec.x; + inGeo.vertices[3 * index + 1] = vec.y; + inGeo.vertices[3 * index + 2] = vec.z; + } markForTessellation(); } @@ -1666,6 +1758,57 @@ public void setNormal(int index, float nx, float ny, float nz) { } + @Override + public void setAttrib(String name, int index, float... values) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setNormal()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.FLOAT, + values.length); + float[] array = inGeo.fattribs.get(name); + for (int i = 0; i < values.length; i++) { + array[attrib.size * index + i] = values[i]; + } + markForTessellation(); + } + + + @Override + public void setAttrib(String name, int index, int... values) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setNormal()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.INT, + values.length); + int[] array = inGeo.iattribs.get(name); + for (int i = 0; i < values.length; i++) { + array[attrib.size * index + i] = values[i]; + } + markForTessellation(); + } + + + @Override + public void setAttrib(String name, int index, boolean... values) { + if (openShape) { + PGraphics.showWarning(INSIDE_BEGIN_END_ERROR, "setNormal()"); + return; + } + + VertexAttribute attrib = attribImpl(name, VertexAttribute.OTHER, PGL.BOOL, + values.length); + byte[] array = inGeo.battribs.get(name); + for (int i = 0; i < values.length; i++) { + array[attrib.size * index + i] = (byte)(values[i]?1:0); + } + markForTessellation(); + } + + @Override public float getTextureU(int index) { return inGeo.texcoords[2 * index + 0]; @@ -1718,8 +1861,8 @@ public void setFill(boolean fill) { PShapeOpenGL child = (PShapeOpenGL) children[i]; child.setFill(fill); } - } else if (this.fill && !fill) { - setFillImpl(0x0); + } else if (this.fill != fill) { + markForTessellation(); } this.fill = fill; } @@ -1749,18 +1892,18 @@ protected void setFillImpl(int fill) { if (image == null) { Arrays.fill(inGeo.colors, 0, inGeo.vertexCount, - PGL.javaToNativeARGB(fillColor)); + PGL.javaToNativeARGB(fillColor)); if (shapeCreated && tessellated && hasPolys) { if (is3D()) { Arrays.fill(tessGeo.polyColors, firstPolyVertex, lastPolyVertex + 1, - PGL.javaToNativeARGB(fillColor)); + PGL.javaToNativeARGB(fillColor)); root.setModifiedPolyColors(firstPolyVertex, lastPolyVertex); } else if (is2D()) { int last1 = lastPolyVertex + 1; if (-1 < firstLineVertex) last1 = firstLineVertex; if (-1 < firstPointVertex) last1 = firstPointVertex; Arrays.fill(tessGeo.polyColors, firstPolyVertex, last1, - PGL.javaToNativeARGB(fillColor)); + PGL.javaToNativeARGB(fillColor)); root.setModifiedPolyColors(firstPolyVertex, last1 - 1); } } @@ -1844,18 +1987,18 @@ protected void setTintImpl(int tint) { if (image != null) { Arrays.fill(inGeo.colors, 0, inGeo.vertexCount, - PGL.javaToNativeARGB(tintColor)); + PGL.javaToNativeARGB(tintColor)); if (shapeCreated && tessellated && hasPolys) { if (is3D()) { Arrays.fill(tessGeo.polyColors, firstPolyVertex, lastPolyVertex + 1, - PGL.javaToNativeARGB(tintColor)); + PGL.javaToNativeARGB(tintColor)); root.setModifiedPolyColors(firstPolyVertex, lastPolyVertex); } else if (is2D()) { int last1 = lastPolyVertex + 1; if (-1 < firstLineVertex) last1 = firstLineVertex; if (-1 < firstPointVertex) last1 = firstPointVertex; Arrays.fill(tessGeo.polyColors, firstPolyVertex, last1, - PGL.javaToNativeARGB(tintColor)); + PGL.javaToNativeARGB(tintColor)); root.setModifiedPolyColors(firstPolyVertex, last1 - 1); } } @@ -1899,20 +2042,31 @@ public void setStroke(boolean stroke) { PShapeOpenGL child = (PShapeOpenGL) children[i]; child.setStroke(stroke); } - } else if (this.stroke != stroke) { - if (this.stroke) { - // Disabling stroke on a shape previously with - // stroke needs a re-tessellation in order to remove - // the additional geometry of lines and/or points. - markForTessellation(); - stroke = false; + this.stroke = stroke; + } else { + setStrokeImpl(stroke); + } + } + + + protected void setStrokeImpl(boolean stroke) { + if (this.stroke != stroke) { + if (stroke) { + // Before there was no stroke, now there is stroke, so current stroke + // color should be copied to the input geometry, and geometry should + // be marked as modified in case it needs to be re-tessellated. + int color = strokeColor; + strokeColor += 1; // Forces a color change + setStrokeImpl(color); } - setStrokeImpl(0x0); + + markForTessellation(); if (is2D() && parent != null) { - ((PShapeOpenGL)parent).strokedTexture(false); + ((PShapeOpenGL)parent).strokedTexture(stroke && image != null); } + + this.stroke = stroke; } - this.stroke = stroke; } @@ -1939,27 +2093,27 @@ protected void setStrokeImpl(int stroke) { strokeColor = stroke; Arrays.fill(inGeo.strokeColors, 0, inGeo.vertexCount, - PGL.javaToNativeARGB(strokeColor)); + PGL.javaToNativeARGB(strokeColor)); if (shapeCreated && tessellated && (hasLines || hasPoints)) { if (hasLines) { if (is3D()) { Arrays.fill(tessGeo.lineColors, firstLineVertex, lastLineVertex + 1, - PGL.javaToNativeARGB(strokeColor)); + PGL.javaToNativeARGB(strokeColor)); root.setModifiedLineColors(firstLineVertex, lastLineVertex); } else if (is2D()) { Arrays.fill(tessGeo.polyColors, firstLineVertex, lastLineVertex + 1, - PGL.javaToNativeARGB(strokeColor)); + PGL.javaToNativeARGB(strokeColor)); root.setModifiedPolyColors(firstLineVertex, lastLineVertex); } } if (hasPoints) { if (is3D()) { Arrays.fill(tessGeo.pointColors, firstPointVertex, lastPointVertex + 1, - PGL.javaToNativeARGB(strokeColor)); + PGL.javaToNativeARGB(strokeColor)); root.setModifiedPointColors(firstPointVertex, lastPointVertex); } else if (is2D()) { Arrays.fill(tessGeo.polyColors, firstPointVertex, lastPointVertex + 1, - PGL.javaToNativeARGB(strokeColor)); + PGL.javaToNativeARGB(strokeColor)); root.setModifiedPolyColors(firstPointVertex, lastPointVertex); } } @@ -2139,18 +2293,18 @@ protected void setAmbientImpl(int ambient) { ambientColor = ambient; Arrays.fill(inGeo.ambient, 0, inGeo.vertexCount, - PGL.javaToNativeARGB(ambientColor)); + PGL.javaToNativeARGB(ambientColor)); if (shapeCreated && tessellated && hasPolys) { if (is3D()) { Arrays.fill(tessGeo.polyAmbient, firstPolyVertex, lastPolyVertex + 1, - PGL.javaToNativeARGB(ambientColor)); + PGL.javaToNativeARGB(ambientColor)); root.setModifiedPolyAmbient(firstPolyVertex, lastPolyVertex); } else if (is2D()) { int last1 = lastPolyVertex + 1; if (-1 < firstLineVertex) last1 = firstLineVertex; if (-1 < firstPointVertex) last1 = firstPointVertex; Arrays.fill(tessGeo.polyAmbient, firstPolyVertex, last1, - PGL.javaToNativeARGB(ambientColor)); + PGL.javaToNativeARGB(ambientColor)); root.setModifiedPolyColors(firstPolyVertex, last1 - 1); } } @@ -2204,18 +2358,18 @@ protected void setSpecularImpl(int specular) { specularColor = specular; Arrays.fill(inGeo.specular, 0, inGeo.vertexCount, - PGL.javaToNativeARGB(specularColor)); + PGL.javaToNativeARGB(specularColor)); if (shapeCreated && tessellated && hasPolys) { if (is3D()) { Arrays.fill(tessGeo.polySpecular, firstPolyVertex, lastPolyVertex + 1, - PGL.javaToNativeARGB(specularColor)); + PGL.javaToNativeARGB(specularColor)); root.setModifiedPolySpecular(firstPolyVertex, lastPolyVertex); } else if (is2D()) { int last1 = lastPolyVertex + 1; if (-1 < firstLineVertex) last1 = firstLineVertex; if (-1 < firstPointVertex) last1 = firstPointVertex; Arrays.fill(tessGeo.polySpecular, firstPolyVertex, last1, - PGL.javaToNativeARGB(specularColor)); + PGL.javaToNativeARGB(specularColor)); root.setModifiedPolyColors(firstPolyVertex, last1 - 1); } } @@ -2267,18 +2421,18 @@ protected void setEmissiveImpl(int emissive) { emissiveColor = emissive; Arrays.fill(inGeo.emissive, 0, inGeo.vertexCount, - PGL.javaToNativeARGB(emissiveColor)); + PGL.javaToNativeARGB(emissiveColor)); if (shapeCreated && tessellated && 0 < tessGeo.polyVertexCount) { if (is3D()) { Arrays.fill(tessGeo.polyEmissive, firstPolyVertex, lastPolyVertex + 1, - PGL.javaToNativeARGB(emissiveColor)); + PGL.javaToNativeARGB(emissiveColor)); root.setModifiedPolyEmissive(firstPolyVertex, lastPolyVertex); } else if (is2D()) { int last1 = lastPolyVertex + 1; if (-1 < firstLineVertex) last1 = firstLineVertex; if (-1 < firstPointVertex) last1 = firstPointVertex; Arrays.fill(tessGeo.polyEmissive, firstPolyVertex, last1, - PGL.javaToNativeARGB(emissiveColor)); + PGL.javaToNativeARGB(emissiveColor)); root.setModifiedPolyColors(firstPolyVertex, last1 - 1); } } @@ -2333,7 +2487,7 @@ protected void setShininessImpl(float shininess) { if (shapeCreated && tessellated && hasPolys) { if (is3D()) { Arrays.fill(tessGeo.polyShininess, firstPolyVertex, lastPolyVertex + 1, - shininess); + shininess); root.setModifiedPolyShininess(firstPolyVertex, lastPolyVertex); } else if (is2D()) { int last1 = lastPolyVertex + 1; @@ -2420,14 +2574,18 @@ public PShape getTessellation() { short[] indices = tessGeo.polyIndices; PShape tess; - if (is3D()) { - tess = PGraphics3D.createShapeImpl(pg, PShape.GEOMETRY); - } else if (is2D()) { - tess = PGraphics2D.createShapeImpl(pg, PShape.GEOMETRY); - } else { - PGraphics.showWarning("This shape is not either 2D or 3D!"); - return null; - } +// if (is3D()) { +// //tess = PGraphics3D.createShapeImpl(pg, PShape.GEOMETRY); +// tess = pg.createShapeFamily(PShape.GEOMETRY); +// } else if (is2D()) { +// //tess = PGraphics2D.createShapeImpl(pg, PShape.GEOMETRY); +// tess = pg.createShapeFamily(PShape.GEOMETRY); +// } else { +// PGraphics.showWarning("This shape is not either 2D or 3D!"); +// return null; +// } + tess = pg.createShapeFamily(PShape.GEOMETRY); + tess.set3D(is3D); // if this is a 3D shape, make the new shape 3D as well tess.beginShape(TRIANGLES); tess.noStroke(); @@ -2586,9 +2744,9 @@ public boolean contains(float x, float y) { if (((inGeo.vertices[3 * i + 1] > y) != (inGeo.vertices[3 * j + 1] > y)) && (x < (inGeo.vertices[3 * j]-inGeo.vertices[3 * i]) * - (y-inGeo.vertices[3 * i + 1]) / - (inGeo.vertices[3 * j + 1]-inGeo.vertices[3 * i + 1]) + - inGeo.vertices[3 * i])) { + (y-inGeo.vertices[3 * i + 1]) / + (inGeo.vertices[3 * j + 1]-inGeo.vertices[3 * i + 1]) + + inGeo.vertices[3 * i])) { c = !c; } } @@ -2676,12 +2834,27 @@ protected void initModified() { protected void tessellate() { - if (root == this && parent == null) { + if (root == this && parent == null) { // Root shape + boolean initAttr = false; + if (polyAttribs == null) { + polyAttribs = PGraphicsOpenGL.newAttributeMap(); + initAttr = true; + } + if (tessGeo == null) { - tessGeo = PGraphicsOpenGL.newTessGeometry(pg, PGraphicsOpenGL.RETAINED); + tessGeo = PGraphicsOpenGL.newTessGeometry(pg, polyAttribs, PGraphicsOpenGL.RETAINED); } tessGeo.clear(); + if (initAttr) { + collectPolyAttribs(); + } + + for (int i = 0; i < polyAttribs.size(); i++) { + VertexAttribute attrib = polyAttribs.get(i); + tessGeo.initAttrib(attrib); + } + tessellateImpl(); // Tessellated arrays are trimmed since they are expanded @@ -2692,6 +2865,31 @@ protected void tessellate() { } + protected void collectPolyAttribs() { + AttributeMap rootAttribs = root.polyAttribs; + tessGeo = root.tessGeo; + + if (family == GROUP) { + for (int i = 0; i < childCount; i++) { + PShapeOpenGL child = (PShapeOpenGL) children[i]; + child.collectPolyAttribs(); + } + } else { + for (int i = 0; i < polyAttribs.size(); i++) { + VertexAttribute attrib = polyAttribs.get(i); + tessGeo.initAttrib(attrib); + if (rootAttribs.containsKey(attrib.name)) { + VertexAttribute rattrib = rootAttribs.get(attrib.name); + if (rattrib.diff(attrib)) { + throw new RuntimeException("Children shapes cannot have different attributes with same name"); + } + } else { + rootAttribs.put(attrib.name, attrib); + } + } + } + } + protected void tessellateImpl() { tessGeo = root.tessGeo; @@ -2703,6 +2901,11 @@ protected void tessellateImpl() { lastPointIndexCache = -1; if (family == GROUP) { + if (polyAttribs == null) { + polyAttribs = PGraphicsOpenGL.newAttributeMap(); + collectPolyAttribs(); + } + for (int i = 0; i < childCount; i++) { PShapeOpenGL child = (PShapeOpenGL) children[i]; child.tessellateImpl(); @@ -2767,7 +2970,7 @@ protected void tessellateImpl() { tessellator.resetCurveVertexCount(); } tessellator.tessellatePolygon(solid, close, - normalMode == NORMAL_MODE_AUTO); + normalMode == NORMAL_MODE_AUTO); if (bez ||quad) restoreBezierVertexSettings(); if (curv) restoreCurveVertexSettings(); } @@ -2835,7 +3038,7 @@ protected void tessellatePoint() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addPoint(x, y, z, fill, stroke); tessellator.tessellatePoints(); @@ -2860,11 +3063,11 @@ protected void tessellateLine() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addLine(x1, y1, z1, - x2, y2, z2, - fill, stroke); + x2, y2, z2, + fill, stroke); tessellator.tessellateLines(); } @@ -2883,12 +3086,12 @@ protected void tessellateTriangle() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addTriangle(x1, y1, 0, - x2, y2, 0, - x3, y3, 0, - fill, stroke); + x2, y2, 0, + x3, y3, 0, + fill, stroke); tessellator.tessellateTriangles(); } @@ -2910,13 +3113,13 @@ protected void tessellateQuad() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addQuad(x1, y1, 0, - x2, y2, 0, - x3, y3, 0, - x4, y4, 0, - stroke); + x2, y2, 0, + x3, y3, 0, + x4, y4, 0, + stroke); tessellator.tessellateQuads(); } @@ -2932,11 +3135,15 @@ protected void tessellateRect() { b = params[1]; c = params[2]; d = params[3]; + rounded = false; if (params.length == 5) { - mode = (int)(params[4]); + tl = params[4]; + tr = params[4]; + br = params[4]; + bl = params[4]; + rounded = true; } - rounded = false; - } else if (params.length == 8 || params.length == 9) { + } else if (params.length == 8) { a = params[0]; b = params[1]; c = params[2]; @@ -2945,34 +3152,31 @@ protected void tessellateRect() { tr = params[5]; br = params[6]; bl = params[7]; - if (params.length == 9) { - mode = (int)(params[8]); - } rounded = true; } float hradius, vradius; switch (mode) { - case CORNERS: - break; - case CORNER: - c += a; d += b; - break; - case RADIUS: - hradius = c; - vradius = d; - c = a + hradius; - d = b + vradius; - a -= hradius; - b -= vradius; - break; - case CENTER: - hradius = c / 2.0f; - vradius = d / 2.0f; - c = a + hradius; - d = b + vradius; - a -= hradius; - b -= vradius; + case CORNERS: + break; + case CORNER: + c += a; d += b; + break; + case RADIUS: + hradius = c; + vradius = d; + c = a + hradius; + d = b + vradius; + a -= hradius; + b -= vradius; + break; + case CENTER: + hradius = c / 2.0f; + vradius = d / 2.0f; + c = a + hradius; + d = b + vradius; + a -= hradius; + b -= vradius; } if (a > c) { @@ -2990,12 +3194,12 @@ protected void tessellateRect() { if (bl > maxRounding) bl = maxRounding; inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); if (rounded) { saveBezierVertexSettings(); inGeo.addRect(a, b, c, d, tl, tr, br, bl, stroke); - tessellator.tessellatePolygon(false, true, true); + tessellator.tessellatePolygon(true, true, true); restoreBezierVertexSettings(); } else { inGeo.addRect(a, b, c, d, stroke); @@ -3013,9 +3217,6 @@ protected void tessellateEllipse() { b = params[1]; c = params[2]; d = params[3]; - if (params.length == 5) { - mode = (int)(params[4]); - } } float x = a; @@ -3049,7 +3250,7 @@ protected void tessellateEllipse() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); inGeo.addEllipse(x, y, w, h, fill, stroke); tessellator.tessellateTriangleFan(); @@ -3060,6 +3261,7 @@ protected void tessellateArc() { float a = 0, b = 0, c = 0, d = 0; float start = 0, stop = 0; int mode = ellipseMode; + int arcMode = 0; if (6 <= params.length) { a = params[0]; @@ -3069,7 +3271,7 @@ protected void tessellateArc() { start = params[4]; stop = params[5]; if (params.length == 7) { - mode = (int)(params[6]); + arcMode = (int)(params[6]); } } @@ -3104,13 +3306,13 @@ protected void tessellateArc() { } if (stop - start > TWO_PI) { - start = 0; - stop = TWO_PI; + // don't change start, it is visible in PIE mode + stop = start + TWO_PI; } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.setNormal(normalX, normalY, normalZ); - inGeo.addArc(x, y, w, h, start, stop, fill, stroke, mode); + inGeo.addArc(x, y, w, h, start, stop, fill, stroke, arcMode); tessellator.tessellateTriangleFan(); } } @@ -3128,7 +3330,7 @@ protected void tessellateBox() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); inGeo.addBox(w, h, d, fill, stroke); tessellator.tessellateQuads(); } @@ -3158,11 +3360,12 @@ protected void tessellateSphere() { } inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); int[] indices = inGeo.addSphere(r, nu, nv, fill, stroke); tessellator.tessellateTriangles(indices); - if (savedDetailU != nu || savedDetailV != nv) { + if ((0 < savedDetailU && savedDetailU != nu) || + (0 < savedDetailV && savedDetailV != nv)) { pg.sphereDetail(savedDetailU, savedDetailV); } } @@ -3172,7 +3375,7 @@ protected void tessellatePath() { if (vertices == null) return; inGeo.setMaterial(fillColor, strokeColor, strokeWeight, - ambientColor, specularColor, emissiveColor, shininess); + ambientColor, specularColor, emissiveColor, shininess); if (vertexCodeCount == 0) { // each point is a simple vertex if (vertices[0].length == 2) { // tessellating 2D vertices @@ -3182,7 +3385,7 @@ protected void tessellatePath() { } else { // drawing 3D vertices for (int i = 0; i < vertexCount; i++) { inGeo.addVertex(vertices[i][X], vertices[i][Y], vertices[i][Z], - VERTEX, false); + VERTEX, false); } } } else { // coded set of vertices @@ -3194,88 +3397,88 @@ protected void tessellatePath() { for (int j = 0; j < vertexCodeCount; j++) { switch (vertexCodes[j]) { - case VERTEX: - inGeo.addVertex(vertices[idx][X], vertices[idx][Y], VERTEX, brk); - brk = false; - idx++; - break; - - case QUADRATIC_VERTEX: - inGeo.addQuadraticVertex(vertices[idx+0][X], vertices[idx+0][Y], 0, - vertices[idx+1][X], vertices[idx+1][Y], 0, - brk); - brk = false; - idx += 2; - break; - - case BEZIER_VERTEX: - inGeo.addBezierVertex(vertices[idx+0][X], vertices[idx+0][Y], 0, - vertices[idx+1][X], vertices[idx+1][Y], 0, - vertices[idx+2][X], vertices[idx+2][Y], 0, - brk); - brk = false; - idx += 3; - break; - - case CURVE_VERTEX: - inGeo.addCurveVertex(vertices[idx][X], vertices[idx][Y], 0, brk); - brk = false; - idx++; - break; - - case BREAK: - brk = true; + case VERTEX: + inGeo.addVertex(vertices[idx][X], vertices[idx][Y], VERTEX, brk); + brk = false; + idx++; + break; + + case QUADRATIC_VERTEX: + inGeo.addQuadraticVertex(vertices[idx+0][X], vertices[idx+0][Y], 0, + vertices[idx+1][X], vertices[idx+1][Y], 0, + brk); + brk = false; + idx += 2; + break; + + case BEZIER_VERTEX: + inGeo.addBezierVertex(vertices[idx+0][X], vertices[idx+0][Y], 0, + vertices[idx+1][X], vertices[idx+1][Y], 0, + vertices[idx+2][X], vertices[idx+2][Y], 0, + brk); + brk = false; + idx += 3; + break; + + case CURVE_VERTEX: + inGeo.addCurveVertex(vertices[idx][X], vertices[idx][Y], 0, brk); + brk = false; + idx++; + break; + + case BREAK: + brk = true; } } } else { // tessellating a 3D path for (int j = 0; j < vertexCodeCount; j++) { switch (vertexCodes[j]) { - case VERTEX: - inGeo.addVertex(vertices[idx][X], vertices[idx][Y], - vertices[idx][Z], brk); - brk = false; - idx++; - break; - - case QUADRATIC_VERTEX: - inGeo.addQuadraticVertex(vertices[idx+0][X], - vertices[idx+0][Y], - vertices[idx+0][Z], - vertices[idx+1][X], - vertices[idx+1][Y], - vertices[idx+0][Z], - brk); - brk = false; - idx += 2; - break; - - case BEZIER_VERTEX: - inGeo.addBezierVertex(vertices[idx+0][X], - vertices[idx+0][Y], - vertices[idx+0][Z], - vertices[idx+1][X], - vertices[idx+1][Y], - vertices[idx+1][Z], - vertices[idx+2][X], - vertices[idx+2][Y], - vertices[idx+2][Z], - brk); - brk = false; - idx += 3; - break; - - case CURVE_VERTEX: - inGeo.addCurveVertex(vertices[idx][X], - vertices[idx][Y], - vertices[idx][Z], - brk); - brk = false; - idx++; - break; - - case BREAK: - brk = true; + case VERTEX: + inGeo.addVertex(vertices[idx][X], vertices[idx][Y], + vertices[idx][Z], brk); + brk = false; + idx++; + break; + + case QUADRATIC_VERTEX: + inGeo.addQuadraticVertex(vertices[idx+0][X], + vertices[idx+0][Y], + vertices[idx+0][Z], + vertices[idx+1][X], + vertices[idx+1][Y], + vertices[idx+0][Z], + brk); + brk = false; + idx += 2; + break; + + case BEZIER_VERTEX: + inGeo.addBezierVertex(vertices[idx+0][X], + vertices[idx+0][Y], + vertices[idx+0][Z], + vertices[idx+1][X], + vertices[idx+1][Y], + vertices[idx+1][Z], + vertices[idx+2][X], + vertices[idx+2][Y], + vertices[idx+2][Z], + brk); + brk = false; + idx += 3; + break; + + case CURVE_VERTEX: + inGeo.addCurveVertex(vertices[idx][X], + vertices[idx][Y], + vertices[idx][Z], + brk); + brk = false; + idx++; + break; + + case BREAK: + brk = true; } } } @@ -3289,7 +3492,7 @@ protected void tessellatePath() { saveCurveVertexSettings(); tessellator.resetCurveVertexCount(); } - tessellator.tessellatePolygon(false, close, true); + tessellator.tessellatePolygon(true, close, true); if (bez || quad) restoreBezierVertexSettings(); if (curv) restoreCurveVertexSettings(); } @@ -3418,16 +3621,16 @@ protected void aggregateImpl() { // this shape before tessellation, so they are applied now. if (hasPolys) { tessGeo.applyMatrixOnPolyGeometry(matrix, - firstPolyVertex, lastPolyVertex); + firstPolyVertex, lastPolyVertex); } if (is3D()) { if (hasLines) { tessGeo.applyMatrixOnLineGeometry(matrix, - firstLineVertex, lastLineVertex); + firstLineVertex, lastLineVertex); } if (hasPoints) { tessGeo.applyMatrixOnPointGeometry(matrix, - firstPointVertex, lastPointVertex); + firstPointVertex, lastPointVertex); } } } @@ -3470,7 +3673,7 @@ protected void updatePolyIndexCache() { // This is a result of how the indices are updated for the // leaf shapes. cache.incCounts(gindex, - cache.indexCount[n], cache.vertexCount[n]); + cache.indexCount[n], cache.vertexCount[n]); } else { gindex = cache.addNew(n); } @@ -3503,7 +3706,7 @@ protected void updatePolyIndexCache() { // to be restarted as well to reflect the new index offset. firstPolyVertex = lastPolyVertex = - cache.vertexOffset[firstPolyIndexCache]; + cache.vertexOffset[firstPolyIndexCache]; for (int n = firstPolyIndexCache; n <= lastPolyIndexCache; n++) { int ioffset = cache.indexOffset[n]; int icount = cache.indexCount[n]; @@ -3516,7 +3719,7 @@ protected void updatePolyIndexCache() { cache.indexOffset[n] = root.polyIndexOffset; } else { tessGeo.incPolyIndices(ioffset, ioffset + icount - 1, - root.polyVertexRel); + root.polyVertexRel); } cache.vertexOffset[n] = root.polyVertexOffset; if (is2D()) { @@ -3538,7 +3741,7 @@ protected void updatePolyIndexCache() { protected boolean startStrokedTex(int n) { return image != null && (n == firstLineIndexCache || - n == firstPointIndexCache); + n == firstPointIndexCache); } @@ -3578,7 +3781,7 @@ protected void updateLineIndexCache() { } else { if (cache.vertexOffset[gindex] == cache.vertexOffset[n]) { cache.incCounts(gindex, cache.indexCount[n], - cache.vertexCount[n]); + cache.vertexCount[n]); } else { gindex = cache.addNew(n); } @@ -3597,7 +3800,7 @@ protected void updateLineIndexCache() { lastLineIndexCache = gindex; } else { firstLineVertex = lastLineVertex = - cache.vertexOffset[firstLineIndexCache]; + cache.vertexOffset[firstLineIndexCache]; for (int n = firstLineIndexCache; n <= lastLineIndexCache; n++) { int ioffset = cache.indexOffset[n]; int icount = cache.indexCount[n]; @@ -3609,7 +3812,7 @@ protected void updateLineIndexCache() { cache.indexOffset[n] = root.lineIndexOffset; } else { tessGeo.incLineIndices(ioffset, ioffset + icount - 1, - root.lineVertexRel); + root.lineVertexRel); } cache.vertexOffset[n] = root.lineVertexOffset; @@ -3646,7 +3849,7 @@ protected void updatePointIndexCache() { // This is a result of how the indices are updated for the // leaf shapes in aggregateImpl(). cache.incCounts(gindex, cache.indexCount[n], - cache.vertexCount[n]); + cache.vertexCount[n]); } else { gindex = cache.addNew(n); } @@ -3657,7 +3860,7 @@ protected void updatePointIndexCache() { if (-1 < child.firstPointVertex) { if (firstPointVertex == -1) firstPointVertex = Integer.MAX_VALUE; firstPointVertex = PApplet.min(firstPointVertex, - child.firstPointVertex); + child.firstPointVertex); } if (-1 < child.lastPointVertex) { lastPointVertex = PApplet.max(lastPointVertex, child.lastPointVertex); @@ -3666,7 +3869,7 @@ protected void updatePointIndexCache() { lastPointIndexCache = gindex; } else { firstPointVertex = lastPointVertex = - cache.vertexOffset[firstPointIndexCache]; + cache.vertexOffset[firstPointIndexCache]; for (int n = firstPointIndexCache; n <= lastPointIndexCache; n++) { int ioffset = cache.indexOffset[n]; int icount = cache.indexCount[n]; @@ -3678,7 +3881,7 @@ protected void updatePointIndexCache() { cache.indexOffset[n] = root.pointIndexOffset; } else { tessGeo.incPointIndices(ioffset, ioffset + icount - 1, - root.pointVertexRel); + root.pointVertexRel); } cache.vertexOffset[n] = root.pointVertexOffset; @@ -3725,70 +3928,79 @@ protected void initPolyBuffers() { int sizei = size * PGL.SIZEOF_INT; tessGeo.updatePolyVerticesBuffer(); - if (glPolyVertex == 0) - glPolyVertex = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex); + if (bufPolyVertex == null) + bufPolyVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.polyVerticesBuffer, glUsage); + tessGeo.polyVerticesBuffer, glUsage); tessGeo.updatePolyColorsBuffer(); - if (glPolyColor == 0) - glPolyColor = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor); + if (bufPolyColor == null) + bufPolyColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyColorsBuffer, glUsage); + tessGeo.polyColorsBuffer, glUsage); tessGeo.updatePolyNormalsBuffer(); - if (glPolyNormal == 0) - glPolyNormal = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal); + if (bufPolyNormal == null) + bufPolyNormal = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 3, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 3 * sizef, - tessGeo.polyNormalsBuffer, glUsage); + tessGeo.polyNormalsBuffer, glUsage); tessGeo.updatePolyTexCoordsBuffer(); - if (glPolyTexcoord == 0) - glPolyTexcoord = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord); + if (bufPolyTexcoord == null) + bufPolyTexcoord = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexcoord.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, - tessGeo.polyTexCoordsBuffer, glUsage); + tessGeo.polyTexCoordsBuffer, glUsage); tessGeo.updatePolyAmbientBuffer(); - if (glPolyAmbient == 0) - glPolyAmbient = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient); + if (bufPolyAmbient == null) + bufPolyAmbient = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyAmbientBuffer, glUsage); + tessGeo.polyAmbientBuffer, glUsage); tessGeo.updatePolySpecularBuffer(); - if (glPolySpecular == 0) - glPolySpecular = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular); + if (bufPolySpecular == null) + bufPolySpecular = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polySpecularBuffer, glUsage); + tessGeo.polySpecularBuffer, glUsage); tessGeo.updatePolyEmissiveBuffer(); - if (glPolyEmissive == 0) - glPolyEmissive = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive); + if (bufPolyEmissive == null) + bufPolyEmissive = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.polyEmissiveBuffer, glUsage); + tessGeo.polyEmissiveBuffer, glUsage); tessGeo.updatePolyShininessBuffer(); - if (glPolyShininess == 0) - glPolyShininess = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess); + if (bufPolyShininess == null) + bufPolyShininess = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizef, - tessGeo.polyShininessBuffer, glUsage); + tessGeo.polyShininessBuffer, glUsage); + + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + tessGeo.updateAttribBuffer(attrib.name); + if (!attrib.bufferCreated()) attrib.createBuffer(pgl); + pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); + pgl.bufferData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(size), + tessGeo.polyAttribBuffers.get(name), glUsage); + } pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); tessGeo.updatePolyIndicesBuffer(); - if (glPolyIndex == 0) - glPolyIndex = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPolyIndex); + if (bufPolyIndex == null) + bufPolyIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPolyIndex.glId); pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.polyIndexCount * PGL.SIZEOF_INDEX, - tessGeo.polyIndicesBuffer, glUsage); + tessGeo.polyIndexCount * PGL.SIZEOF_INDEX, + tessGeo.polyIndicesBuffer, glUsage); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); } @@ -3800,35 +4012,35 @@ protected void initLineBuffers() { int sizei = size * PGL.SIZEOF_INT; tessGeo.updateLineVerticesBuffer(); - if (glLineVertex == 0) - glLineVertex = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex); + if (bufLineVertex == null) + bufLineVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.lineVerticesBuffer, glUsage); + tessGeo.lineVerticesBuffer, glUsage); tessGeo.updateLineColorsBuffer(); - if (glLineColor == 0) - glLineColor = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor); + if (bufLineColor == null) + bufLineColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.lineColorsBuffer, glUsage); + tessGeo.lineColorsBuffer, glUsage); tessGeo.updateLineDirectionsBuffer(); - if (glLineAttrib == 0) - glLineAttrib = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib); + if (bufLineAttrib == null) + bufLineAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.lineDirectionsBuffer, glUsage); + tessGeo.lineDirectionsBuffer, glUsage); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); tessGeo.updateLineIndicesBuffer(); - if (glLineIndex == 0) - glLineIndex = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glLineIndex); + if (bufLineIndex == null) + bufLineIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufLineIndex.glId); pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.lineIndexCount * PGL.SIZEOF_INDEX, - tessGeo.lineIndicesBuffer, glUsage); + tessGeo.lineIndexCount * PGL.SIZEOF_INDEX, + tessGeo.lineIndicesBuffer, glUsage); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); } @@ -3840,35 +4052,35 @@ protected void initPointBuffers() { int sizei = size * PGL.SIZEOF_INT; tessGeo.updatePointVerticesBuffer(); - if (glPointVertex == 0) - glPointVertex = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex); + if (bufPointVertex == null) + bufPointVertex = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 4, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 4 * sizef, - tessGeo.pointVerticesBuffer, glUsage); + tessGeo.pointVerticesBuffer, glUsage); tessGeo.updatePointColorsBuffer(); - if (glPointColor == 0) - glPointColor = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor); + if (bufPointColor == null) + bufPointColor = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 1, PGL.SIZEOF_INT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); pgl.bufferData(PGL.ARRAY_BUFFER, sizei, - tessGeo.pointColorsBuffer, glUsage); + tessGeo.pointColorsBuffer, glUsage); tessGeo.updatePointOffsetsBuffer(); - if (glPointAttrib == 0) - glPointAttrib = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib); + if (bufPointAttrib == null) + bufPointAttrib = new VertexBuffer(pg, PGL.ARRAY_BUFFER, 2, PGL.SIZEOF_FLOAT); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); pgl.bufferData(PGL.ARRAY_BUFFER, 2 * sizef, - tessGeo.pointOffsetsBuffer, glUsage); + tessGeo.pointOffsetsBuffer, glUsage); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); tessGeo.updatePointIndicesBuffer(); - if (glPointIndex == 0) - glPointIndex = PGraphicsOpenGL.createVertexBufferObject(context, pgl); - pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, glPointIndex); + if (bufPointIndex == null) + bufPointIndex = new VertexBuffer(pg, PGL.ELEMENT_ARRAY_BUFFER, 1, PGL.SIZEOF_INDEX, true); + pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, bufPointIndex.glId); pgl.bufferData(PGL.ELEMENT_ARRAY_BUFFER, - tessGeo.pointIndexCount * PGL.SIZEOF_INDEX, - tessGeo.pointIndicesBuffer, glUsage); + tessGeo.pointIndexCount * PGL.SIZEOF_INDEX, + tessGeo.pointIndicesBuffer, glUsage); pgl.bindBuffer(PGL.ELEMENT_ARRAY_BUFFER, 0); } @@ -3877,163 +4089,30 @@ protected void initPointBuffers() { protected boolean contextIsOutdated() { boolean outdated = !pgl.contextIsCurrent(context); if (outdated) { - // Removing the VBOs from the renderer's list so they - // doesn't get deleted by OpenGL. The VBOs were already - // automatically disposed when the old context was - // destroyed. - PGraphicsOpenGL.removeVertexBufferObject(glPolyVertex, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyColor, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyNormal, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyTexcoord, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyAmbient, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolySpecular, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyEmissive, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyShininess, context); - PGraphicsOpenGL.removeVertexBufferObject(glPolyIndex, context); - - PGraphicsOpenGL.removeVertexBufferObject(glLineVertex, context); - PGraphicsOpenGL.removeVertexBufferObject(glLineColor, context); - PGraphicsOpenGL.removeVertexBufferObject(glLineAttrib, context); - PGraphicsOpenGL.removeVertexBufferObject(glLineIndex, context); - - PGraphicsOpenGL.removeVertexBufferObject(glPointVertex, context); - PGraphicsOpenGL.removeVertexBufferObject(glPointColor, context); - PGraphicsOpenGL.removeVertexBufferObject(glPointAttrib, context); - PGraphicsOpenGL.removeVertexBufferObject(glPointIndex, context); - - // The OpenGL resources have been already deleted - // when the context changed. We only need to zero - // them to avoid deleting them again when the GC - // runs the finalizers of the disposed object. - glPolyVertex = 0; - glPolyColor = 0; - glPolyNormal = 0; - glPolyTexcoord = 0; - glPolyAmbient = 0; - glPolySpecular = 0; - glPolyEmissive = 0; - glPolyShininess = 0; - glPolyIndex = 0; - - glLineVertex = 0; - glLineColor = 0; - glLineAttrib = 0; - glLineIndex = 0; - - glPointVertex = 0; - glPointColor = 0; - glPointAttrib = 0; - glPointIndex = 0; - } - return outdated; - } - - - /////////////////////////////////////////////////////////// - - // - - // Deletion methods - - - protected void dispose() { - deletePolyBuffers(); - deleteLineBuffers(); - deletePointBuffers(); - } - - - protected void deletePolyBuffers() { - if (glPolyVertex != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyVertex, context, pgl); - glPolyVertex = 0; - } - - if (glPolyColor != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyColor, context, pgl); - glPolyColor = 0; - } - - if (glPolyNormal != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyNormal, context, pgl); - glPolyNormal = 0; - } - - if (glPolyTexcoord != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyTexcoord, context, pgl); - glPolyTexcoord = 0; - } - - if (glPolyAmbient != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyAmbient, context, pgl); - glPolyAmbient = 0; - } - - if (glPolySpecular != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolySpecular, context, pgl); - glPolySpecular = 0; - } - - if (glPolyEmissive != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyEmissive, context, pgl); - glPolyEmissive = 0; - } - - if (glPolyShininess != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyShininess, context, pgl); - glPolyShininess = 0; - } - - if (glPolyIndex != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPolyIndex, context, pgl); - glPolyIndex = 0; - } - } - - - protected void deleteLineBuffers() { - if (glLineVertex != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glLineVertex, context, pgl); - glLineVertex = 0; - } - - if (glLineColor != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glLineColor, context, pgl); - glLineColor = 0; - } - - if (glLineAttrib != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glLineAttrib, context, pgl); - glLineAttrib = 0; - } - - if (glLineIndex != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glLineIndex, context, pgl); - glLineIndex = 0; - } - } - - - protected void deletePointBuffers() { - if (glPointVertex != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPointVertex, context, pgl); - glPointVertex = 0; - } - - if (glPointColor != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPointColor, context, pgl); - glPointColor = 0; - } + bufPolyVertex.dispose(); + bufPolyColor.dispose(); + bufPolyNormal.dispose(); + bufPolyTexcoord.dispose(); + bufPolyAmbient.dispose(); + bufPolySpecular.dispose(); + bufPolyEmissive.dispose(); + bufPolyShininess.dispose(); + for (VertexAttribute attrib: polyAttribs.values()) { + attrib.buf.dispose(); + } + bufPolyIndex.dispose(); - if (glPointAttrib != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPointAttrib, context, pgl); - glPointAttrib = 0; - } + bufLineVertex.dispose(); + bufLineColor.dispose(); + bufLineAttrib.dispose(); + bufLineIndex.dispose(); - if (glPointIndex != 0) { - PGraphicsOpenGL.deleteVertexBufferObject(glPointIndex, context, pgl); - glPointIndex = 0; + bufPointVertex.dispose(); + bufPointColor.dispose(); + bufPointAttrib.dispose(); + bufPointIndex.dispose(); } + return outdated; } @@ -4117,6 +4196,17 @@ protected void updateGeometryImpl() { firstModifiedPolyShininess = PConstants.MAX_INT; lastModifiedPolyShininess = PConstants.MIN_INT; } + for (String name: polyAttribs.keySet()) { + VertexAttribute attrib = polyAttribs.get(name); + if (attrib.modified) { + int offset = firstModifiedPolyVertex; + int size = lastModifiedPolyVertex - offset + 1; + copyPolyAttrib(attrib, offset, size); + attrib.modified = false; + attrib.firstModified = PConstants.MAX_INT; + attrib.lastModified = PConstants.MIN_INT; + } + } if (modifiedLineVertices) { int offset = firstModifiedLineVertex; @@ -4174,10 +4264,10 @@ protected void updateGeometryImpl() { protected void copyPolyVertices(int offset, int size) { tessGeo.updatePolyVerticesBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyVertex); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyVertex.glId); tessGeo.polyVerticesBuffer.position(4 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.polyVerticesBuffer); + 4 * size * PGL.SIZEOF_FLOAT, tessGeo.polyVerticesBuffer); tessGeo.polyVerticesBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4185,10 +4275,10 @@ protected void copyPolyVertices(int offset, int size) { protected void copyPolyColors(int offset, int size) { tessGeo.updatePolyColorsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyColor); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyColor.glId); tessGeo.polyColorsBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polyColorsBuffer); + size * PGL.SIZEOF_INT, tessGeo.polyColorsBuffer); tessGeo.polyColorsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4196,10 +4286,10 @@ protected void copyPolyColors(int offset, int size) { protected void copyPolyNormals(int offset, int size) { tessGeo.updatePolyNormalsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyNormal); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyNormal.glId); tessGeo.polyNormalsBuffer.position(3 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 3 * offset * PGL.SIZEOF_FLOAT, - 3 * size * PGL.SIZEOF_FLOAT, tessGeo.polyNormalsBuffer); + 3 * size * PGL.SIZEOF_FLOAT, tessGeo.polyNormalsBuffer); tessGeo.polyNormalsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4207,10 +4297,10 @@ protected void copyPolyNormals(int offset, int size) { protected void copyPolyTexCoords(int offset, int size) { tessGeo.updatePolyTexCoordsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyTexcoord); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyTexcoord.glId); tessGeo.polyTexCoordsBuffer.position(2 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT, - 2 * size * PGL.SIZEOF_FLOAT, tessGeo.polyTexCoordsBuffer); + 2 * size * PGL.SIZEOF_FLOAT, tessGeo.polyTexCoordsBuffer); tessGeo.polyTexCoordsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4218,10 +4308,10 @@ protected void copyPolyTexCoords(int offset, int size) { protected void copyPolyAmbient(int offset, int size) { tessGeo.updatePolyAmbientBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyAmbient); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyAmbient.glId); tessGeo.polyAmbientBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polyAmbientBuffer); + size * PGL.SIZEOF_INT, tessGeo.polyAmbientBuffer); tessGeo.polyAmbientBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4229,10 +4319,10 @@ protected void copyPolyAmbient(int offset, int size) { protected void copyPolySpecular(int offset, int size) { tessGeo.updatePolySpecularBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolySpecular); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolySpecular.glId); tessGeo.polySpecularBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polySpecularBuffer); + size * PGL.SIZEOF_INT, tessGeo.polySpecularBuffer); tessGeo.polySpecularBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4240,10 +4330,10 @@ protected void copyPolySpecular(int offset, int size) { protected void copyPolyEmissive(int offset, int size) { tessGeo.updatePolyEmissiveBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyEmissive); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyEmissive.glId); tessGeo.polyEmissiveBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.polyEmissiveBuffer); + size * PGL.SIZEOF_INT, tessGeo.polyEmissiveBuffer); tessGeo.polyEmissiveBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4251,21 +4341,33 @@ protected void copyPolyEmissive(int offset, int size) { protected void copyPolyShininess(int offset, int size) { tessGeo.updatePolyShininessBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPolyShininess); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPolyShininess.glId); tessGeo.polyShininessBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_FLOAT, - size * PGL.SIZEOF_FLOAT, tessGeo.polyShininessBuffer); + size * PGL.SIZEOF_FLOAT, tessGeo.polyShininessBuffer); tessGeo.polyShininessBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } + protected void copyPolyAttrib(VertexAttribute attrib, int offset, int size) { + tessGeo.updateAttribBuffer(attrib.name, offset, size); + pgl.bindBuffer(PGL.ARRAY_BUFFER, attrib.buf.glId); + Buffer buf = tessGeo.polyAttribBuffers.get(attrib.name); + buf.position(attrib.size * offset); + pgl.bufferSubData(PGL.ARRAY_BUFFER, attrib.sizeInBytes(offset), + attrib.sizeInBytes(size), buf); + buf.rewind(); + pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); + } + + protected void copyLineVertices(int offset, int size) { tessGeo.updateLineVerticesBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineVertex); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineVertex.glId); tessGeo.lineVerticesBuffer.position(4 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineVerticesBuffer); + 4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineVerticesBuffer); tessGeo.lineVerticesBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4273,10 +4375,10 @@ protected void copyLineVertices(int offset, int size) { protected void copyLineColors(int offset, int size) { tessGeo.updateLineColorsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineColor); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineColor.glId); tessGeo.lineColorsBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT, tessGeo.lineColorsBuffer); + size * PGL.SIZEOF_INT, tessGeo.lineColorsBuffer); tessGeo.lineColorsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4284,10 +4386,10 @@ protected void copyLineColors(int offset, int size) { protected void copyLineAttributes(int offset, int size) { tessGeo.updateLineDirectionsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glLineAttrib); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufLineAttrib.glId); tessGeo.lineDirectionsBuffer.position(4 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineDirectionsBuffer); + 4 * size * PGL.SIZEOF_FLOAT, tessGeo.lineDirectionsBuffer); tessGeo.lineDirectionsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4295,10 +4397,10 @@ protected void copyLineAttributes(int offset, int size) { protected void copyPointVertices(int offset, int size) { tessGeo.updatePointVerticesBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointVertex); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointVertex.glId); tessGeo.pointVerticesBuffer.position(4 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 4 * offset * PGL.SIZEOF_FLOAT, - 4 * size * PGL.SIZEOF_FLOAT, tessGeo.pointVerticesBuffer); + 4 * size * PGL.SIZEOF_FLOAT, tessGeo.pointVerticesBuffer); tessGeo.pointVerticesBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4306,10 +4408,10 @@ protected void copyPointVertices(int offset, int size) { protected void copyPointColors(int offset, int size) { tessGeo.updatePointColorsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointColor); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointColor.glId); tessGeo.pointColorsBuffer.position(offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, offset * PGL.SIZEOF_INT, - size * PGL.SIZEOF_INT,tessGeo.pointColorsBuffer); + size * PGL.SIZEOF_INT,tessGeo.pointColorsBuffer); tessGeo.pointColorsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4317,10 +4419,10 @@ protected void copyPointColors(int offset, int size) { protected void copyPointAttributes(int offset, int size) { tessGeo.updatePointOffsetsBuffer(offset, size); - pgl.bindBuffer(PGL.ARRAY_BUFFER, glPointAttrib); + pgl.bindBuffer(PGL.ARRAY_BUFFER, bufPointAttrib.glId); tessGeo.pointOffsetsBuffer.position(2 * offset); pgl.bufferSubData(PGL.ARRAY_BUFFER, 2 * offset * PGL.SIZEOF_FLOAT, - 2 * size * PGL.SIZEOF_FLOAT, tessGeo.pointOffsetsBuffer); + 2 * size * PGL.SIZEOF_FLOAT, tessGeo.pointOffsetsBuffer); tessGeo.pointOffsetsBuffer.rewind(); pgl.bindBuffer(PGL.ARRAY_BUFFER, 0); } @@ -4390,6 +4492,14 @@ protected void setModifiedPolyShininess(int first, int last) { } + protected void setModifiedPolyAttrib(VertexAttribute attrib, int first, int last) { + if (first < attrib.firstModified) attrib.firstModified = first; + if (last > attrib.lastModified) attrib.lastModified = last; + attrib.modified = true; + modified = true; + } + + protected void setModifiedLineVertices(int first, int last) { if (first < firstModifiedLineVertex) firstModifiedLineVertex = first; if (last > lastModifiedLineVertex) lastModifiedLineVertex = last; @@ -4509,7 +4619,6 @@ public void enableStyle() { } - // Applies the styles of g. @Override protected void styles(PGraphics g) { if (g instanceof PGraphicsOpenGL) { @@ -4556,9 +4665,11 @@ protected void styles(PGraphics g) { // Rendering methods + /* public void draw() { draw(pg); } + */ @Override @@ -4583,32 +4694,119 @@ public void draw(PGraphics g) { } render(gl, tex); } - } else { render(gl, image); } - post(gl); } } else { - // The renderer is not PGraphicsOpenGL, which probably - // means that the draw() method is being called by the - // recorder. We just use the default drawing from the - // parent class. - super.draw(g); + if (family == GEOMETRY) { + inGeoToVertices(); + } + pre(g); + drawImpl(g); + post(g); + } + } + + + private void inGeoToVertices() { + vertexCount = 0; + vertexCodeCount = 0; + if (inGeo.codeCount == 0) { + for (int i = 0; i < inGeo.vertexCount; i++) { + int index = 3 * i; + float x = inGeo.vertices[index++]; + float y = inGeo.vertices[index ]; + super.vertex(x, y); + } + } else { + int v; + float x, y; + float cx, cy; + float x2, y2, x3, y3, x4, y4; + int idx = 0; + boolean insideContour = false; + + for (int j = 0; j < inGeo.codeCount; j++) { + switch (inGeo.codes[j]) { + + case VERTEX: + v = 3 * idx; + x = inGeo.vertices[v++]; + y = inGeo.vertices[v ]; + super.vertex(x, y); + + idx++; + break; + + case QUADRATIC_VERTEX: + v = 3 * idx; + cx = inGeo.vertices[v++]; + cy = inGeo.vertices[v]; + + v = 3 * (idx + 1); + x3 = inGeo.vertices[v++]; + y3 = inGeo.vertices[v]; + + super.quadraticVertex(cx, cy, x3, y3); + + idx += 2; + break; + + case BEZIER_VERTEX: + v = 3 * idx; + x2 = inGeo.vertices[v++]; + y2 = inGeo.vertices[v ]; + + v = 3 * (idx + 1); + x3 = inGeo.vertices[v++]; + y3 = inGeo.vertices[v ]; + + v = 3 * (idx + 2); + x4 = inGeo.vertices[v++]; + y4 = inGeo.vertices[v ]; + + super.bezierVertex(x2, y2, x3, y3, x4, y4); + + idx += 3; + break; + + case CURVE_VERTEX: + v = 3 * idx; + x = inGeo.vertices[v++]; + y = inGeo.vertices[v ]; + + super.curveVertex(x, y); + + idx++; + break; + + case BREAK: + if (insideContour) { + super.endContourImpl(); + } + super.beginContourImpl(); + insideContour = true; + } + } + if (insideContour) { + super.endContourImpl(); + } } } // Returns true if some child shapes below this one either - // use different texture maps or have stroked textures, + // use different texture maps (or only one texture is used by some while + // others are untextured), or have stroked textures, // so they cannot rendered in a single call. // Or accurate 2D mode is enabled, which forces each // shape to be rendered separately. protected boolean fragmentedGroup(PGraphicsOpenGL g) { return g.getHint(DISABLE_OPTIMIZED_STROKE) || - (textures != null && 1 < textures.size()) || - strokedTexture; + (textures != null && (1 < textures.size() || untexChild)) || + strokedTexture; } @@ -4651,7 +4849,7 @@ protected void render(PGraphicsOpenGL g, PImage texture) { if (root == null) { // Some error. Root should never be null. At least it should be 'this'. throw new RuntimeException("Error rendering PShapeOpenGL, root shape is " + - "null"); + "null"); } if (hasPolys) { @@ -4693,9 +4891,9 @@ protected void renderPolys(PGraphicsOpenGL g, PImage textureImage) { IndexCache cache = tessGeo.polyIndexCache; for (int n = firstPolyIndexCache; n <= lastPolyIndexCache; n++) { if (is3D() || (tex != null && (firstLineIndexCache == -1 || - n < firstLineIndexCache) && - (firstPointIndexCache == -1 || - n < firstPointIndexCache))) { + n < firstLineIndexCache) && + (firstPointIndexCache == -1 || + n < firstPointIndexCache))) { // Rendering fill triangles, which can be lit and textured. if (!renderingFill) { shader = g.getPolyShader(g.lights, tex != null); @@ -4728,37 +4926,48 @@ protected void renderPolys(PGraphicsOpenGL g, PImage textureImage) { int icount = cache.indexCount[n]; int voffset = cache.vertexOffset[n]; - shader.setVertexAttribute(root.glPolyVertex, 4, PGL.FLOAT, - 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.setColorAttribute(root.glPolyColor, 4, PGL.UNSIGNED_BYTE, - 0, 4 * voffset * PGL.SIZEOF_BYTE); + shader.setVertexAttribute(root.bufPolyVertex.glId, 4, PGL.FLOAT, + 0, 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(root.bufPolyColor.glId, 4, PGL.UNSIGNED_BYTE, + 0, 4 * voffset * PGL.SIZEOF_BYTE); if (g.lights) { - shader.setNormalAttribute(root.glPolyNormal, 3, PGL.FLOAT, - 0, 3 * voffset * PGL.SIZEOF_FLOAT); - shader.setAmbientAttribute(root.glPolyAmbient, 4, PGL.UNSIGNED_BYTE, - 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setSpecularAttribute(root.glPolySpecular, 4, PGL.UNSIGNED_BYTE, - 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setEmissiveAttribute(root.glPolyEmissive, 4, PGL.UNSIGNED_BYTE, - 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setShininessAttribute(root.glPolyShininess, 1, PGL.FLOAT, - 0, voffset * PGL.SIZEOF_FLOAT); + shader.setNormalAttribute(root.bufPolyNormal.glId, 3, PGL.FLOAT, + 0, 3 * voffset * PGL.SIZEOF_FLOAT); + shader.setAmbientAttribute(root.bufPolyAmbient.glId, 4, PGL.UNSIGNED_BYTE, + 0, 4 * voffset * PGL.SIZEOF_BYTE); + shader.setSpecularAttribute(root.bufPolySpecular.glId, 4, PGL.UNSIGNED_BYTE, + 0, 4 * voffset * PGL.SIZEOF_BYTE); + shader.setEmissiveAttribute(root.bufPolyEmissive.glId, 4, PGL.UNSIGNED_BYTE, + 0, 4 * voffset * PGL.SIZEOF_BYTE); + shader.setShininessAttribute(root.bufPolyShininess.glId, 1, PGL.FLOAT, + 0, voffset * PGL.SIZEOF_FLOAT); } if (g.lights || needNormals) { - shader.setNormalAttribute(root.glPolyNormal, 3, PGL.FLOAT, - 0, 3 * voffset * PGL.SIZEOF_FLOAT); + shader.setNormalAttribute(root.bufPolyNormal.glId, 3, PGL.FLOAT, + 0, 3 * voffset * PGL.SIZEOF_FLOAT); } if (tex != null || needTexCoords) { - shader.setTexcoordAttribute(root.glPolyTexcoord, 2, PGL.FLOAT, - 0, 2 * voffset * PGL.SIZEOF_FLOAT); + shader.setTexcoordAttribute(root.bufPolyTexcoord.glId, 2, PGL.FLOAT, + 0, 2 * voffset * PGL.SIZEOF_FLOAT); shader.setTexture(tex); } - shader.draw(root.glPolyIndex, icount, ioffset); + for (VertexAttribute attrib: polyAttribs.values()) { + if (!attrib.active(shader)) continue; + attrib.bind(pgl); + shader.setAttributeVBO(attrib.glLoc, attrib.buf.glId, + attrib.tessSize, attrib.type, + attrib.isColor(), 0, attrib.sizeInBytes(voffset)); + } + + shader.draw(root.bufPolyIndex.glId, icount, ioffset); } + for (VertexAttribute attrib: polyAttribs.values()) { + if (attrib.active(shader)) attrib.unbind(pgl); + } if (shader != null && shader.bound()) { shader.unbind(); } @@ -4870,14 +5079,14 @@ protected void renderLines(PGraphicsOpenGL g) { int icount = cache.indexCount[n]; int voffset = cache.vertexOffset[n]; - shader.setVertexAttribute(root.glLineVertex, 4, PGL.FLOAT, - 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.setColorAttribute(root.glLineColor, 4, PGL.UNSIGNED_BYTE, - 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setLineAttribute(root.glLineAttrib, 4, PGL.FLOAT, - 0, 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setVertexAttribute(root.bufLineVertex.glId, 4, PGL.FLOAT, + 0, 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(root.bufLineColor.glId, 4, PGL.UNSIGNED_BYTE, + 0, 4 * voffset * PGL.SIZEOF_BYTE); + shader.setLineAttribute(root.bufLineAttrib.glId, 4, PGL.FLOAT, + 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.draw(root.glLineIndex, icount, ioffset); + shader.draw(root.bufLineIndex.glId, icount, ioffset); } shader.unbind(); @@ -4967,14 +5176,14 @@ protected void renderPoints(PGraphicsOpenGL g) { int icount = cache.indexCount[n]; int voffset = cache.vertexOffset[n]; - shader.setVertexAttribute(root.glPointVertex, 4, PGL.FLOAT, - 0, 4 * voffset * PGL.SIZEOF_FLOAT); - shader.setColorAttribute(root.glPointColor, 4, PGL.UNSIGNED_BYTE, - 0, 4 * voffset * PGL.SIZEOF_BYTE); - shader.setPointAttribute(root.glPointAttrib, 2, PGL.FLOAT, - 0, 2 * voffset * PGL.SIZEOF_FLOAT); + shader.setVertexAttribute(root.bufPointVertex.glId, 4, PGL.FLOAT, + 0, 4 * voffset * PGL.SIZEOF_FLOAT); + shader.setColorAttribute(root.bufPointColor.glId, 4, PGL.UNSIGNED_BYTE, + 0, 4 * voffset * PGL.SIZEOF_BYTE); + shader.setPointAttribute(root.bufPointAttrib.glId, 2, PGL.FLOAT, + 0, 2 * voffset * PGL.SIZEOF_FLOAT); - shader.draw(root.glPointIndex, icount, ioffset); + shader.draw(root.bufPointIndex.glId, icount, ioffset); } shader.unbind(); @@ -5008,9 +5217,9 @@ protected void rawPoints(PGraphicsOpenGL g) { if (0 < size) { // round point weight = +size / 0.5f; perim = PApplet.min(PGraphicsOpenGL.MAX_POINT_ACCURACY, - PApplet.max(PGraphicsOpenGL.MIN_POINT_ACCURACY, - (int) (TWO_PI * weight / - PGraphicsOpenGL.POINT_ACCURACY_FACTOR))) + 1; + PApplet.max(PGraphicsOpenGL.MIN_POINT_ACCURACY, + (int) (TWO_PI * weight / + PGraphicsOpenGL.POINT_ACCURACY_FACTOR))) + 1; } else { // Square point weight = -size / 0.5f; perim = 5; diff --git a/libs/processing-core/src/main/java/processing/opengl/PSurfaceGLES.java b/libs/processing-core/src/main/java/processing/opengl/PSurfaceGLES.java new file mode 100644 index 000000000..b9a86ae87 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/opengl/PSurfaceGLES.java @@ -0,0 +1,604 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License version 2.1 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.opengl; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.opengles.GL10; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.pm.ConfigurationInfo; +import android.opengl.GLSurfaceView; +import android.opengl.GLSurfaceView.EGLConfigChooser; +import android.opengl.GLSurfaceView.Renderer; +import android.service.wallpaper.WallpaperService; +import android.support.wearable.watchface.Gles2WatchFaceService; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; + +import processing.android.AppComponent; +import processing.android.PFragment; +import processing.core.PApplet; +import processing.core.PGraphics; +import processing.core.PSurfaceNone; + +public class PSurfaceGLES extends PSurfaceNone { + public PGLES pgl; + private SurfaceViewGLES glsurf; + + public PSurfaceGLES() { } + + public PSurfaceGLES(PGraphics graphics, AppComponent component, SurfaceHolder holder) { + this.sketch = graphics.parent; + this.graphics = graphics; + this.component = component; + this.pgl = (PGLES)((PGraphicsOpenGL)graphics).pgl; + if (component.getKind() == AppComponent.FRAGMENT) { + PFragment frag = (PFragment)component; + activity = frag.getActivity(); + surfaceView = new SurfaceViewGLES(activity, null); + } else if (component.getKind() == AppComponent.WALLPAPER) { + wallpaper = (WallpaperService)component; + surfaceView = new SurfaceViewGLES(wallpaper, holder); + } else if (component.getKind() == AppComponent.WATCHFACE) { + watchface = (Gles2WatchFaceService)component; + // Set as ready here, as watch faces don't have a surface view with a + // surfaceCreate() event to do it. + surfaceReady = true; + } + glsurf = (SurfaceViewGLES)surfaceView; + } + + @Override + public void dispose() { + super.dispose(); + if (glsurf != null) { + glsurf.dispose(); + glsurf = null; + } + } + + /////////////////////////////////////////////////////////// + + // Thread handling + + @Override + protected void callDraw() { + component.requestDraw(); + if (component.canDraw() && glsurf != null) { + glsurf.requestRender(); + } + } + + /////////////////////////////////////////////////////////// + + // GL SurfaceView + + public class SurfaceViewGLES extends GLSurfaceView { + SurfaceHolder holder; + + public SurfaceViewGLES(Context context, SurfaceHolder holder) { + super(context); + this.holder = holder; + + // Check if the system supports OpenGL ES 2.0. + final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo(); + final boolean supportsGLES2 = configurationInfo.reqGlEsVersion >= 0x20000; + + if (!supportsGLES2) { + throw new RuntimeException("OpenGL ES 2.0 is not supported by this device."); + } + + SurfaceHolder h = getHolder(); + h.addCallback(this); + + // Tells the default EGLContextFactory and EGLConfigChooser to create an GLES2 context. + setEGLContextClientVersion(PGLES.version); + setPreserveEGLContextOnPause(true); + + int samples = sketch.sketchSmooth(); + if (1 < samples) { + setEGLConfigChooser(getConfigChooser(samples)); + } else { + // use default EGL config chooser for now... +// setEGLConfigChooser(getConfigChooser(5, 6, 5, 4, 16, 1, samples)); + + // Some notes on how to choose an EGL configuration: + // https://github.com/mapbox/mapbox-gl-native/issues/574 + // http://malideveloper.arm.com/sample-code/selecting-the-correct-eglconfig/ + } + + + // The renderer can be set only once. + setRenderer(getRenderer()); + setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + surfaceReady = false; // Will be ready when the surfaceCreated() event is called + } + + @Override + public SurfaceHolder getHolder() { + if (holder == null) { + return super.getHolder(); + } else { + return holder; + } + } + + public void dispose() { + super.destroyDrawingCache(); + super.onDetachedFromWindow(); + } + + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + super.surfaceChanged(holder, format, w, h); + +// if (PApplet.DEBUG) { +// System.out.println("SketchSurfaceView3D.surfaceChanged() " + w + " " + h); +// } +// System.out.println("SketchSurfaceView3D.surfaceChanged() " + w + " " + h + " " + sketch); +// sketch.surfaceChanged(); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + super.surfaceCreated(holder); + surfaceReady = true; + if (requestedThreadStart) { + startThread(); + } + if (PApplet.DEBUG) { + System.out.println("surfaceCreated()"); + } + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + super.surfaceDestroyed(holder); + if (PApplet.DEBUG) { + System.out.println("surfaceDestroyed()"); + } + } + + + // Inform the view that the window focus has changed. + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + sketch.surfaceWindowFocusChanged(hasFocus); + } + + // Do we need these to capture events...? + @Override + public boolean onTouchEvent(MotionEvent event) { + boolean fullscreen = sketch.width == sketch.displayWidth && + sketch.height == sketch.displayHeight; + if (fullscreen && PApplet.SDK < 19) { + // The best we can do pre-KitKat to keep the navigation bar hidden + setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + return sketch.surfaceTouchEvent(event); + } + + @Override + public boolean onKeyDown(int code, android.view.KeyEvent event) { + sketch.surfaceKeyDown(code, event); + return super.onKeyDown(code, event); + } + + @Override + public boolean onKeyUp(int code, android.view.KeyEvent event) { + sketch.surfaceKeyUp(code, event); + return super.onKeyUp(code, event); + } + } + + /////////////////////////////////////////////////////////// + + // Android specific classes (Renderer, ConfigChooser) + + + public RendererGLES getRenderer() { +// renderer = new AndroidRenderer(); +// return renderer; + return new RendererGLES(); + } + + + public ContextFactoryGLES getContextFactory() { + return new ContextFactoryGLES(); + } + + + public ConfigChooserGLES getConfigChooser(int samples) { + return new ConfigChooserGLES(5, 6, 5, 4, 16, 1, samples); +// return new AndroidConfigChooser(8, 8, 8, 8, 16, 8, samples); + } + + + public ConfigChooserGLES getConfigChooser(int r, int g, int b, int a, + int d, int s, int samples) { + return new ConfigChooserGLES(r, g, b, a, d, s, samples); + } + + + protected class RendererGLES implements Renderer { + + public RendererGLES() { + } + + @Override + public void onDrawFrame(GL10 igl) { + pgl.getGL(igl); + sketch.handleDraw(); + } + + @Override + public void onSurfaceChanged(GL10 igl, int iwidth, int iheight) { + if (PApplet.DEBUG) { + System.out.println("AndroidRenderer.onSurfaceChanged() " + iwidth + " " + iheight); + } + + pgl.getGL(igl); + + // Here is where we should initialize native libs... + // lib.init(iwidth, iheight); + +// sketch.surfaceChanged(); +// graphics.surfaceChanged(); +// +// sketch.setSize(iwidth, iheight); +// graphics.setSize(sketch.sketchWidth(), sketch.sketchHeight()); + sketch.surfaceChanged(); + sketch.setSize(iwidth, iheight); + } + + @Override + public void onSurfaceCreated(GL10 igl, EGLConfig config) { + pgl.init(igl); + } + } + + + protected class ContextFactoryGLES implements + GLSurfaceView.EGLContextFactory { + public EGLContext createContext(EGL10 egl, EGLDisplay display, + EGLConfig eglConfig) { + int[] attrib_list = { PGLES.EGL_CONTEXT_CLIENT_VERSION, PGLES.version, + EGL10.EGL_NONE }; + EGLContext context = egl.eglCreateContext(display, eglConfig, + EGL10.EGL_NO_CONTEXT, + attrib_list); + return context; + } + + public void destroyContext(EGL10 egl, EGLDisplay display, + EGLContext context) { + egl.eglDestroyContext(display, context); + } + } + + + protected class ConfigChooserGLES implements EGLConfigChooser { + // Desired size (in bits) for the rgba color, depth and stencil buffers. + public int redTarget; + public int greenTarget; + public int blueTarget; + public int alphaTarget; + public int depthTarget; + public int stencilTarget; + + // Actual rgba color, depth and stencil sizes (in bits) supported by the + // device. + public int redBits; + public int greenBits; + public int blueBits; + public int alphaBits; + public int depthBits; + public int stencilBits; + public int[] tempValue = new int[1]; + + public int numSamples; + + /* + The GLES2 extensions supported are: + GL_OES_rgb8_rgba8 GL_OES_depth24 GL_OES_vertex_half_float + GL_OES_texture_float GL_OES_texture_half_float + GL_OES_element_index_uint GL_OES_mapbuffer + GL_OES_fragment_precision_high GL_OES_compressed_ETC1_RGB8_texture + GL_OES_EGL_image GL_OES_required_internalformat GL_OES_depth_texture + GL_OES_get_program_binary GL_OES_packed_depth_stencil + GL_OES_standard_derivatives GL_OES_vertex_array_object GL_OES_egl_sync + GL_EXT_multi_draw_arrays GL_EXT_texture_format_BGRA8888 + GL_EXT_discard_framebuffer GL_EXT_shader_texture_lod + GL_IMG_shader_binary GL_IMG_texture_compression_pvrtc + GL_IMG_texture_stream2 GL_IMG_texture_npot + GL_IMG_texture_format_BGRA8888 GL_IMG_read_format + GL_IMG_program_binary GL_IMG_multisampled_render_to_texture + */ + + /* + // The attributes we want in the frame buffer configuration for Processing. + // For more details on other attributes, see: + // http://www.khronos.org/opengles/documentation/opengles1_0/html/eglChooseConfig.html + protected int[] configAttribsGL_MSAA = { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_ALPHA_SIZE, 4, + EGL10.EGL_DEPTH_SIZE, 16, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_SAMPLE_BUFFERS, 1, + EGL10.EGL_SAMPLES, 2, + EGL10.EGL_NONE }; + + protected int[] configAttribsGL_CovMSAA = { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_ALPHA_SIZE, 4, + EGL10.EGL_DEPTH_SIZE, 16, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_COVERAGE_BUFFERS_NV, 1, + EGL_COVERAGE_SAMPLES_NV, 2, + EGL10.EGL_NONE }; + + protected int[] configAttribsGL_NoMSAA = { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_ALPHA_SIZE, 4, + EGL10.EGL_DEPTH_SIZE, 16, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE }; + + protected int[] configAttribsGL_Good = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, + EGL10.EGL_DEPTH_SIZE, 16, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE }; + + protected int[] configAttribsGL_TestMSAA = { + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_SAMPLE_BUFFERS, 1, + EGL10.EGL_SAMPLES, 2, + EGL10.EGL_NONE }; + */ + + protected int[] attribsNoMSAA = { + EGL10.EGL_RENDERABLE_TYPE, PGLES.EGL_OPENGL_ES2_BIT, + EGL10.EGL_SAMPLE_BUFFERS, 0, + EGL10.EGL_NONE }; + + public ConfigChooserGLES(int rbits, int gbits, int bbits, int abits, + int dbits, int sbits, int samples) { + redTarget = rbits; + greenTarget = gbits; + blueTarget = bbits; + alphaTarget = abits; + depthTarget = dbits; + stencilTarget = sbits; + numSamples = samples; + } + + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + EGLConfig[] configs = null; + if (1 < numSamples) { + int[] attribs = new int[] { + EGL10.EGL_RENDERABLE_TYPE, PGLES.EGL_OPENGL_ES2_BIT, + EGL10.EGL_SAMPLE_BUFFERS, 1, + EGL10.EGL_SAMPLES, numSamples, + EGL10.EGL_NONE }; + configs = chooseConfigWithAttribs(egl, display, attribs); + if (configs == null) { + // No normal multisampling config was found. Try to create a + // coverage multisampling configuration, for the nVidia Tegra2. + // See the EGL_NV_coverage_sample documentation. + int[] attribsCov = { + EGL10.EGL_RENDERABLE_TYPE, PGLES.EGL_OPENGL_ES2_BIT, + PGLES.EGL_COVERAGE_BUFFERS_NV, 1, + PGLES.EGL_COVERAGE_SAMPLES_NV, numSamples, + EGL10.EGL_NONE }; + configs = chooseConfigWithAttribs(egl, display, attribsCov); + if (configs == null) { + configs = chooseConfigWithAttribs(egl, display, attribsNoMSAA); + } else { + PGLES.usingMultisampling = true; + PGLES.usingCoverageMultisampling = true; + PGLES.multisampleCount = numSamples; + } + } else { + PGLES.usingMultisampling = true; + PGLES.usingCoverageMultisampling = false; + PGLES.multisampleCount = numSamples; + } + } else { + configs = chooseConfigWithAttribs(egl, display, attribsNoMSAA); + } + + if (configs == null) { + throw new IllegalArgumentException("No EGL configs match configSpec"); + } + + if (PApplet.DEBUG) { + for (EGLConfig config : configs) { + String configStr = "P3D - selected EGL config : " + + printConfig(egl, display, config); + System.out.println(configStr); + } + } + + // Now return the configuration that best matches the target one. + return chooseBestConfig(egl, display, configs); + } + + public EGLConfig chooseBestConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + EGLConfig bestConfig = null; + float bestScore = Float.MAX_VALUE; + + for (EGLConfig config : configs) { + int gl = findConfigAttrib(egl, display, config, + EGL10.EGL_RENDERABLE_TYPE, 0); + boolean isGLES2 = (gl & PGLES.EGL_OPENGL_ES2_BIT) != 0; + if (isGLES2) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + + float score = 0.20f * PApplet.abs(r - redTarget) + + 0.20f * PApplet.abs(g - greenTarget) + + 0.20f * PApplet.abs(b - blueTarget) + + 0.15f * PApplet.abs(a - alphaTarget) + + 0.15f * PApplet.abs(d - depthTarget) + + 0.10f * PApplet.abs(s - stencilTarget); + + if (score < bestScore) { + // We look for the config closest to the target config. + // Closeness is measured by the score function defined above: + // we give more weight to the RGB components, followed by the + // alpha, depth and finally stencil bits. + bestConfig = config; + bestScore = score; + + redBits = r; + greenBits = g; + blueBits = b; + alphaBits = a; + depthBits = d; + stencilBits = s; + } + } + } + + if (PApplet.DEBUG) { + String configStr = "P3D - selected EGL config : " + + printConfig(egl, display, bestConfig); + System.out.println(configStr); + } + return bestConfig; + } + + protected String printConfig(EGL10 egl, EGLDisplay display, + EGLConfig config) { + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + int type = findConfigAttrib(egl, display, config, + EGL10.EGL_RENDERABLE_TYPE, 0); + int nat = findConfigAttrib(egl, display, config, + EGL10.EGL_NATIVE_RENDERABLE, 0); + int bufSize = findConfigAttrib(egl, display, config, + EGL10.EGL_BUFFER_SIZE, 0); + int bufSurf = findConfigAttrib(egl, display, config, + EGL10.EGL_RENDER_BUFFER, 0); + + return String.format("EGLConfig rgba=%d%d%d%d depth=%d stencil=%d", + r,g,b,a,d,s) + + " type=" + type + + " native=" + nat + + " buffer size=" + bufSize + + " buffer surface=" + bufSurf + + String.format(" caveat=0x%04x", + findConfigAttrib(egl, display, config, + EGL10.EGL_CONFIG_CAVEAT, 0)); + } + + protected int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + if (egl.eglGetConfigAttrib(display, config, attribute, tempValue)) { + return tempValue[0]; + } + return defaultValue; + } + + protected EGLConfig[] chooseConfigWithAttribs(EGL10 egl, + EGLDisplay display, + int[] configAttribs) { + // Get the number of minimally matching EGL configurations + int[] configCounts = new int[1]; + egl.eglChooseConfig(display, configAttribs, null, 0, configCounts); + + int count = configCounts[0]; + + if (count <= 0) { + //throw new IllegalArgumentException("No EGL configs match configSpec"); + return null; + } + + // Allocate then read the array of minimally matching EGL configs + EGLConfig[] configs = new EGLConfig[count]; + egl.eglChooseConfig(display, configAttribs, configs, count, configCounts); + return configs; + + // Get the number of minimally matching EGL configurations +// int[] num_config = new int[1]; +// egl.eglChooseConfig(display, configAttribsGL, null, 0, num_config); +// +// int numConfigs = num_config[0]; +// +// if (numConfigs <= 0) { +// throw new IllegalArgumentException("No EGL configs match configSpec"); +// } +// +// // Allocate then read the array of minimally matching EGL configs +// EGLConfig[] configs = new EGLConfig[numConfigs]; +// egl.eglChooseConfig(display, configAttribsGL, configs, numConfigs, +// num_config); + + } + } +} diff --git a/core/src/processing/opengl/Texture.java b/libs/processing-core/src/main/java/processing/opengl/Texture.java similarity index 86% rename from core/src/processing/opengl/Texture.java rename to libs/processing-core/src/main/java/processing/opengl/Texture.java index 5ffd60b0b..29d8ba4c5 100644 --- a/core/src/processing/opengl/Texture.java +++ b/libs/processing-core/src/main/java/processing/opengl/Texture.java @@ -1,12 +1,15 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + /* Part of the Processing project - http://processing.org - Copyright (c) 2011-12 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. + License as published by the Free Software Foundation, version 2.1. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -24,9 +27,12 @@ import processing.core.PApplet; import processing.core.PConstants; import processing.core.PGraphics; +import processing.opengl.PGraphicsOpenGL.GLResourceTexture; + import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.IntBuffer; +import java.util.Arrays; import java.util.LinkedList; import java.util.NoSuchElementException; @@ -82,6 +88,7 @@ public class Texture implements PConstants { public int glWrapT; public int glWidth; public int glHeight; + private GLResourceTexture glres; protected PGraphicsOpenGL pg; protected PGL pgl; // The interface between Processing and OpenGL. @@ -100,6 +107,10 @@ public class Texture implements PConstants { protected int[] rgbaPixels = null; protected IntBuffer pixelBuffer = null; + + protected int[] edgePixels = null; + protected IntBuffer edgeBuffer = null; + protected FrameBuffer tempFbo = null; protected int pixBufUpdateCount = 0; protected int rgbaPixUpdateCount = 0; @@ -161,18 +172,6 @@ public Texture(PGraphicsOpenGL pg, int width, int height, Object params) { } - @Override - protected void finalize() throws Throwable { - try { - if (glName != 0) { - PGraphicsOpenGL.finalizeTextureObject(glName, context); - } - } finally { - super.finalize(); - } - } - - //////////////////////////////////////////////////////////// // Init, resize methods @@ -247,8 +246,7 @@ public void init(int width, int height, public void resize(int wide, int high) { - // Marking the texture object as finalized so it is deleted - // when creating the new texture. + // Disposing current resources. dispose(); // Creating new texture with the appropriate size. @@ -340,82 +338,21 @@ public void set(int[] pixels, int x, int y, int w, int h, int format) { } pgl.bindTexture(glTarget, glName); + loadPixels(w * h); + convertToRGBA(pixels, format, w, h); + if (invertedX) flipArrayOnX(rgbaPixels, 1); + if (invertedY) flipArrayOnY(rgbaPixels, 1); + updatePixelBuffer(rgbaPixels); + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixelBuffer); + fillEdges(x, y, w, h); + if (usingMipmaps) { if (PGraphicsOpenGL.autoMipmapGenSupported) { - // Automatic mipmap generation. - loadPixels(w * h); - convertToRGBA(pixels, format, w, h); - updatePixelBuffer(rgbaPixels); - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixelBuffer); pgl.generateMipmap(glTarget); } else { - // TODO: finish manual mipmap generation, replacing Bitmap with AWT's BufferedImage, - // making it work in npot textures (embed npot tex into larger pot tex?), subregions, - // and moving GLUtils.texImage2D (originally from Android SDK) into PGL. - // Actually, this whole code should go into PGL, so the Android implementation can - // use Bitmap, and desktop use BufferedImage. - - /* - if (w != width || h != height) { - System.err.println("Sorry but I don't know how to generate mipmaps for a subregion."); - return; - } - - // Code by Mike Miller obtained from here: - // http://insanitydesign.com/wp/2009/08/01/android-opengl-es-mipmaps/ - int w0 = glWidth; - int h0 = glHeight; - int[] argbPixels = new int[w0 * h0]; - convertToARGB(pixels, argbPixels, format); - int level = 0; - int denom = 1; - - // We create a Bitmap because then we use its built-in filtered downsampling - // functionality. - Bitmap bitmap = Bitmap.createBitmap(w0, h0, Config.ARGB_8888); - bitmap.setPixels(argbPixels, 0, w0, 0, 0, w0, h0); - - while (w0 >= 1 || h0 >= 1) { - //First of all, generate the texture from our bitmap and set it to the according level - GLUtils.texImage2D(glTarget, level, bitmap, 0); - - // We are done. - if (w0 == 1 && h0 == 1) { - break; - } - - // Increase the mipmap level - level++; - denom *= 2; - - // Downsampling bitmap. We must eventually arrive to the 1x1 level, - // and if the width and height are different, there will be a few 1D - // texture levels just before. - // This update formula also allows for NPOT resolutions. - w0 = PApplet.max(1, PApplet.floor((float)glWidth / denom)); - h0 = PApplet.max(1, PApplet.floor((float)glHeight / denom)); - // (see getScaledInstance in AWT Image) - Bitmap bitmap2 = Bitmap.createScaledBitmap(bitmap, w0, h0, true); - - // Clean up - bitmap.recycle(); - bitmap = bitmap2; - } - */ - - loadPixels(w * h); - convertToRGBA(pixels, format, w, h); - updatePixelBuffer(rgbaPixels); - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixelBuffer); + manualMipmap(); } - } else { - loadPixels(w * h); - convertToRGBA(pixels, format, w, h); - updatePixelBuffer(rgbaPixels); - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixelBuffer); } pgl.bindTexture(glTarget, 0); @@ -472,20 +409,17 @@ public void setNative(IntBuffer pixBuf, int x, int y, int w, int h) { } pgl.bindTexture(glTarget, glName); + pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, + pixBuf); + fillEdges(x, y, w, h); + if (usingMipmaps) { if (PGraphicsOpenGL.autoMipmapGenSupported) { - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixBuf); pgl.generateMipmap(glTarget); } else { - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixBuf); + manualMipmap(); } - } else { - pgl.texSubImage2D(glTarget, 0, x, y, w, h, PGL.RGBA, PGL.UNSIGNED_BYTE, - pixBuf); } - pgl.bindTexture(glTarget, 0); if (enabledTex) { pgl.disableTexturing(glTarget); @@ -576,49 +510,60 @@ public boolean usingMipmaps() { public void usingMipmaps(boolean mipmaps, int sampling) { + int glMagFilter0 = glMagFilter; + int glMinFilter0 = glMinFilter; if (mipmaps) { - if (glMinFilter != PGL.LINEAR_MIPMAP_NEAREST && - glMinFilter != PGL.LINEAR_MIPMAP_LINEAR) { - if (sampling == POINT) { - glMagFilter = PGL.NEAREST; - glMinFilter = PGL.NEAREST; - } else if (sampling == LINEAR) { - glMagFilter = PGL.NEAREST; - glMinFilter = - PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; - } else if (sampling == BILINEAR) { - glMagFilter = PGL.LINEAR; - glMinFilter = - PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; - } else if (sampling == TRILINEAR) { - glMagFilter = PGL.LINEAR; - glMinFilter = - PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR; - } else { - throw new RuntimeException("Unknown texture filtering mode"); - } + if (sampling == POINT) { + glMagFilter = PGL.NEAREST; + glMinFilter = PGL.NEAREST; + usingMipmaps = false; + } else if (sampling == LINEAR) { + glMagFilter = PGL.NEAREST; + glMinFilter = + PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; + usingMipmaps = true; + } else if (sampling == BILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = + PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR; + usingMipmaps = true; + } else if (sampling == TRILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = + PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_LINEAR : PGL.LINEAR; + usingMipmaps = true; + } else { + throw new RuntimeException("Unknown texture filtering mode"); } - - usingMipmaps = true; } else { - if (glMinFilter == PGL.LINEAR_MIPMAP_NEAREST || - glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) { + usingMipmaps = false; + if (sampling == POINT) { + glMagFilter = PGL.NEAREST; + glMinFilter = PGL.NEAREST; + } else if (sampling == LINEAR) { + glMagFilter = PGL.NEAREST; glMinFilter = PGL.LINEAR; + } else if (sampling == BILINEAR || sampling == TRILINEAR) { + glMagFilter = PGL.LINEAR; + glMinFilter = PGL.LINEAR; + } else { + throw new RuntimeException("Unknown texture filtering mode"); } - usingMipmaps = false; } - bind(); - pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); - pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter); - if (usingMipmaps) { - if (PGraphicsOpenGL.autoMipmapGenSupported) { - pgl.generateMipmap(glTarget); - } else { - // TODO: need manual generation here.. + if (glMagFilter0 != glMagFilter || glMinFilter0 != glMinFilter) { + bind(); + pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); + pgl.texParameteri(glTarget, PGL.TEXTURE_MAG_FILTER, glMagFilter); + if (usingMipmaps) { + if (PGraphicsOpenGL.autoMipmapGenSupported) { + pgl.generateMipmap(glTarget); + } else { + manualMipmap(); + } } + unbind(); } - unbind(); } @@ -705,6 +650,23 @@ public void invertedY(boolean v) { } + public int currentSampling() { + if (glMagFilter == PGL.NEAREST && glMinFilter == PGL.NEAREST) { + return POINT; + } else if (glMagFilter == PGL.NEAREST && + glMinFilter == (PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR)) { + return LINEAR; + } else if (glMagFilter == PGL.LINEAR && + glMinFilter == (PGL.MIPMAPS_ENABLED ? PGL.LINEAR_MIPMAP_NEAREST : PGL.LINEAR)) { + return BILINEAR; + } else if (glMagFilter == PGL.LINEAR && + glMinFilter == PGL.LINEAR_MIPMAP_LINEAR) { + return TRILINEAR; + } else { + return -1; + } + } + //////////////////////////////////////////////////////////// // Bind/unbind @@ -833,6 +795,12 @@ protected void updatePixelBuffer(int[] pixels) { } + protected void manualMipmap() { + // TODO: finish manual mipmap generation, + // https://github.com/processing/processing/issues/3335 + } + + //////////////////////////////////////////////////////////// // Buffer sink interface. @@ -1174,7 +1142,7 @@ protected void allocate() { } context = pgl.getCurrentContext(); - glName = PGraphicsOpenGL.createTextureObject(context, pgl); + glres = new GLResourceTexture(this); pgl.bindTexture(glTarget, glName); pgl.texParameteri(glTarget, PGL.TEXTURE_MIN_FILTER, glMinFilter); @@ -1208,8 +1176,9 @@ protected void allocate() { * Marks the texture object for deletion. */ protected void dispose() { - if (glName != 0) { - PGraphicsOpenGL.finalizeTextureObject(glName, context); + if (glres != null) { + glres.dispose(); + glres = null; glName = 0; } } @@ -1218,14 +1187,7 @@ protected void dispose() { protected boolean contextIsOutdated() { boolean outdated = !pgl.contextIsCurrent(context); if (outdated) { - // Removing the texture object from the renderer's list so it - // doesn't get deleted by OpenGL. The texture object was - // automatically disposed when the old context was destroyed. - PGraphicsOpenGL.removeTextureObject(glName, context); - - // And then set the id to zero, so it doesn't try to be - // deleted when the object's finalizer is invoked by the GC. - glName = 0; + dispose(); } return outdated; } @@ -1264,27 +1226,29 @@ protected void copyTexture(Texture tex, int x, int y, int w, int h, // FBO copy: pg.pushFramebuffer(); pg.setFramebuffer(tempFbo); - // Clear the color buffer to make sure that the alpha channel is set to - // full transparency - pgl.clearColor(0, 0, 0, 0); - pgl.clear(PGL.COLOR_BUFFER_BIT); + // Replaces anything that this texture might contain in the area being + // replaced by the new one. + pg.pushStyle(); + pg.blendMode(REPLACE); if (scale) { // Rendering tex into "this", and scaling the source rectangle // to cover the entire destination region. - pgl.drawTexture(tex.glTarget, tex.glName, - tex.glWidth, tex.glHeight, tempFbo.width, tempFbo.height, + pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, + 0, 0, tempFbo.width, tempFbo.height, 1, x, y, x + w, y + h, 0, 0, width, height); } else { // Rendering tex into "this" but without scaling so the contents // of the source texture fall in the corresponding texels of the // destination. - pgl.drawTexture(tex.glTarget, tex.glName, - tex.glWidth, tex.glHeight, tempFbo.width, tempFbo.height, + pgl.drawTexture(tex.glTarget, tex.glName, tex.glWidth, tex.glHeight, + 0, 0, tempFbo.width, tempFbo.height, 1, x, y, x + w, y + h, x, y, x + w, y + h); } + pgl.flush(); // Needed to make sure that the change in this texture is + // available immediately. + pg.popStyle(); pg.popFramebuffer(); - updateTexels(x, y, w, h); } @@ -1304,21 +1268,28 @@ protected void copyTexture(int texTarget, int texName, // FBO copy: pg.pushFramebuffer(); pg.setFramebuffer(tempFbo); + // Replaces anything that this texture might contain in the area being + // replaced by the new one. + pg.pushStyle(); + pg.blendMode(REPLACE); if (scale) { // Rendering tex into "this", and scaling the source rectangle // to cover the entire destination region. - pgl.drawTexture(texTarget, texName, - texWidth, texHeight, tempFbo.width, tempFbo.height, + pgl.drawTexture(texTarget, texName, texWidth, texHeight, + 0, 0, tempFbo.width, tempFbo.height, x, y, w, h, 0, 0, width, height); } else { // Rendering tex into "this" but without scaling so the contents // of the source texture fall in the corresponding texels of the // destination. - pgl.drawTexture(texTarget, texName, - texWidth, texHeight, tempFbo.width, tempFbo.height, + pgl.drawTexture(texTarget, texName, texWidth, texHeight, + 0, 0, tempFbo.width, tempFbo.height, x, y, w, h, x, y, w, h); } + pgl.flush(); // Needed to make sure that the change in this texture is + // available immediately. + pg.popStyle(); pg.popFramebuffer(); updateTexels(x, y, w, h); } @@ -1504,6 +1475,44 @@ protected void setParameters(Parameters params) { } + protected void fillEdges(int x, int y, int w, int h) { + if ((width < glWidth || height < glHeight) && (x + w == width || y + h == height)) { + if (x + w == width) { + int ew = glWidth - width; + edgePixels = new int[h * ew]; + for (int i = 0; i < h; i++) { + int c = rgbaPixels[i * w + (w - 1)]; + Arrays.fill(edgePixels, i * ew, (i + 1) * ew, c); + } + edgeBuffer = PGL.updateIntBuffer(edgeBuffer, edgePixels, true); + pgl.texSubImage2D(glTarget, 0, width, y, ew, h, PGL.RGBA, + PGL.UNSIGNED_BYTE, edgeBuffer); + } + + if (y + h == height) { + int eh = glHeight - height; + edgePixels = new int[eh * w]; + for (int i = 0; i < eh; i++) { + System.arraycopy(rgbaPixels, (h - 1) * w, edgePixels, i * w, w); + } + edgeBuffer = PGL.updateIntBuffer(edgeBuffer, edgePixels, true); + pgl.texSubImage2D(glTarget, 0, x, height, w, eh, PGL.RGBA, + PGL.UNSIGNED_BYTE, edgeBuffer); + } + + if (x + w == width && y + h == height) { + int ew = glWidth - width; + int eh = glHeight - height; + int c = rgbaPixels[w * h - 1]; + edgePixels = new int[eh * ew]; + Arrays.fill(edgePixels, 0, eh * ew, c); + edgeBuffer = PGL.updateIntBuffer(edgeBuffer, edgePixels, true); + pgl.texSubImage2D(glTarget, 0, width, height, ew, eh, PGL.RGBA, + PGL.UNSIGNED_BYTE, edgeBuffer); + } + } + } + /////////////////////////////////////////////////////////////////////////// // Parameters object diff --git a/libs/processing-core/src/main/java/processing/opengl/VertexBuffer.java b/libs/processing-core/src/main/java/processing/opengl/VertexBuffer.java new file mode 100644 index 000000000..114428b03 --- /dev/null +++ b/libs/processing-core/src/main/java/processing/opengl/VertexBuffer.java @@ -0,0 +1,88 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2004-12 Ben Fry and Casey Reas + Copyright (c) 2001-04 Massachusetts Institute of Technology + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.opengl; + +import processing.opengl.PGraphicsOpenGL.GLResourceVertexBuffer; + +// TODO: need to combine with PGraphicsOpenGL.VertexAttribute +public class VertexBuffer { + static protected final int INIT_VERTEX_BUFFER_SIZE = 256; + static protected final int INIT_INDEX_BUFFER_SIZE = 512; + + public int glId; + int target; + int elementSize; + int ncoords; + boolean index; + + protected PGL pgl; // The interface between Processing and OpenGL. + protected int context; // The context that created this texture. + private GLResourceVertexBuffer glres; + + VertexBuffer(PGraphicsOpenGL pg, int target, int ncoords, int esize) { + this(pg, target, ncoords, esize, false); + } + + VertexBuffer(PGraphicsOpenGL pg, int target, int ncoords, int esize, boolean index) { + pgl = pg.pgl; + context = pgl.createEmptyContext(); + + this.target = target; + this.ncoords = ncoords; + this.elementSize = esize; + this.index = index; + create(); + init(); + } + + protected void create() { + context = pgl.getCurrentContext(); + glres = new GLResourceVertexBuffer(this); + } + + protected void init() { + int size = index ? ncoords * INIT_INDEX_BUFFER_SIZE * elementSize : + ncoords * INIT_VERTEX_BUFFER_SIZE * elementSize; + pgl.bindBuffer(target, glId); + pgl.bufferData(target, size, null, PGL.STATIC_DRAW); + } + + protected void dispose() { + if (glres != null) { + glres.dispose(); + glId = 0; + glres = null; + } + } + + protected boolean contextIsOutdated() { + boolean outdated = !pgl.contextIsCurrent(context); + if (outdated) { + dispose(); + } + return outdated; + } + +} diff --git a/core/src/processing/opengl/tess/ActiveRegion.java b/libs/processing-core/src/main/java/processing/opengl/tess/ActiveRegion.java similarity index 100% rename from core/src/processing/opengl/tess/ActiveRegion.java rename to libs/processing-core/src/main/java/processing/opengl/tess/ActiveRegion.java diff --git a/core/src/processing/opengl/tess/CachedVertex.java b/libs/processing-core/src/main/java/processing/opengl/tess/CachedVertex.java similarity index 100% rename from core/src/processing/opengl/tess/CachedVertex.java rename to libs/processing-core/src/main/java/processing/opengl/tess/CachedVertex.java diff --git a/core/src/processing/opengl/tess/Dict.java b/libs/processing-core/src/main/java/processing/opengl/tess/Dict.java similarity index 100% rename from core/src/processing/opengl/tess/Dict.java rename to libs/processing-core/src/main/java/processing/opengl/tess/Dict.java diff --git a/core/src/processing/opengl/tess/DictNode.java b/libs/processing-core/src/main/java/processing/opengl/tess/DictNode.java similarity index 100% rename from core/src/processing/opengl/tess/DictNode.java rename to libs/processing-core/src/main/java/processing/opengl/tess/DictNode.java diff --git a/core/src/processing/opengl/tess/GLUface.java b/libs/processing-core/src/main/java/processing/opengl/tess/GLUface.java similarity index 100% rename from core/src/processing/opengl/tess/GLUface.java rename to libs/processing-core/src/main/java/processing/opengl/tess/GLUface.java diff --git a/core/src/processing/opengl/tess/GLUhalfEdge.java b/libs/processing-core/src/main/java/processing/opengl/tess/GLUhalfEdge.java similarity index 100% rename from core/src/processing/opengl/tess/GLUhalfEdge.java rename to libs/processing-core/src/main/java/processing/opengl/tess/GLUhalfEdge.java diff --git a/core/src/processing/opengl/tess/GLUmesh.java b/libs/processing-core/src/main/java/processing/opengl/tess/GLUmesh.java similarity index 100% rename from core/src/processing/opengl/tess/GLUmesh.java rename to libs/processing-core/src/main/java/processing/opengl/tess/GLUmesh.java diff --git a/core/src/processing/opengl/tess/GLUtessellatorImpl.java b/libs/processing-core/src/main/java/processing/opengl/tess/GLUtessellatorImpl.java similarity index 100% rename from core/src/processing/opengl/tess/GLUtessellatorImpl.java rename to libs/processing-core/src/main/java/processing/opengl/tess/GLUtessellatorImpl.java diff --git a/core/src/processing/opengl/tess/GLUvertex.java b/libs/processing-core/src/main/java/processing/opengl/tess/GLUvertex.java similarity index 100% rename from core/src/processing/opengl/tess/GLUvertex.java rename to libs/processing-core/src/main/java/processing/opengl/tess/GLUvertex.java diff --git a/core/src/processing/opengl/tess/Geom.java b/libs/processing-core/src/main/java/processing/opengl/tess/Geom.java similarity index 100% rename from core/src/processing/opengl/tess/Geom.java rename to libs/processing-core/src/main/java/processing/opengl/tess/Geom.java diff --git a/core/src/processing/opengl/tess/Mesh.java b/libs/processing-core/src/main/java/processing/opengl/tess/Mesh.java similarity index 100% rename from core/src/processing/opengl/tess/Mesh.java rename to libs/processing-core/src/main/java/processing/opengl/tess/Mesh.java diff --git a/core/src/processing/opengl/tess/Normal.java b/libs/processing-core/src/main/java/processing/opengl/tess/Normal.java similarity index 100% rename from core/src/processing/opengl/tess/Normal.java rename to libs/processing-core/src/main/java/processing/opengl/tess/Normal.java diff --git a/core/src/processing/opengl/tess/PGLU.java b/libs/processing-core/src/main/java/processing/opengl/tess/PGLU.java similarity index 100% rename from core/src/processing/opengl/tess/PGLU.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PGLU.java diff --git a/core/src/processing/opengl/tess/PGLUtessellator.java b/libs/processing-core/src/main/java/processing/opengl/tess/PGLUtessellator.java similarity index 100% rename from core/src/processing/opengl/tess/PGLUtessellator.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PGLUtessellator.java diff --git a/core/src/processing/opengl/tess/PGLUtessellatorCallback.java b/libs/processing-core/src/main/java/processing/opengl/tess/PGLUtessellatorCallback.java similarity index 100% rename from core/src/processing/opengl/tess/PGLUtessellatorCallback.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PGLUtessellatorCallback.java diff --git a/core/src/processing/opengl/tess/PGLUtessellatorCallbackAdapter.java b/libs/processing-core/src/main/java/processing/opengl/tess/PGLUtessellatorCallbackAdapter.java similarity index 100% rename from core/src/processing/opengl/tess/PGLUtessellatorCallbackAdapter.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PGLUtessellatorCallbackAdapter.java diff --git a/core/src/processing/opengl/tess/PriorityQ.java b/libs/processing-core/src/main/java/processing/opengl/tess/PriorityQ.java similarity index 100% rename from core/src/processing/opengl/tess/PriorityQ.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PriorityQ.java diff --git a/core/src/processing/opengl/tess/PriorityQHeap.java b/libs/processing-core/src/main/java/processing/opengl/tess/PriorityQHeap.java similarity index 100% rename from core/src/processing/opengl/tess/PriorityQHeap.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PriorityQHeap.java diff --git a/core/src/processing/opengl/tess/PriorityQSort.java b/libs/processing-core/src/main/java/processing/opengl/tess/PriorityQSort.java similarity index 100% rename from core/src/processing/opengl/tess/PriorityQSort.java rename to libs/processing-core/src/main/java/processing/opengl/tess/PriorityQSort.java diff --git a/core/src/processing/opengl/tess/Render.java b/libs/processing-core/src/main/java/processing/opengl/tess/Render.java similarity index 100% rename from core/src/processing/opengl/tess/Render.java rename to libs/processing-core/src/main/java/processing/opengl/tess/Render.java diff --git a/core/src/processing/opengl/tess/Sweep.java b/libs/processing-core/src/main/java/processing/opengl/tess/Sweep.java similarity index 100% rename from core/src/processing/opengl/tess/Sweep.java rename to libs/processing-core/src/main/java/processing/opengl/tess/Sweep.java diff --git a/core/src/processing/opengl/tess/TessMono.java b/libs/processing-core/src/main/java/processing/opengl/tess/TessMono.java similarity index 100% rename from core/src/processing/opengl/tess/TessMono.java rename to libs/processing-core/src/main/java/processing/opengl/tess/TessMono.java diff --git a/core/src/processing/opengl/tess/TessState.java b/libs/processing-core/src/main/java/processing/opengl/tess/TessState.java similarity index 100% rename from core/src/processing/opengl/tess/TessState.java rename to libs/processing-core/src/main/java/processing/opengl/tess/TessState.java diff --git a/libs/processing-vr/build.gradle b/libs/processing-vr/build.gradle new file mode 100644 index 000000000..06328948e --- /dev/null +++ b/libs/processing-vr/build.gradle @@ -0,0 +1,24 @@ +plugins { + id 'com.android.library' +} + +android { + namespace "processing.vr" + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 33 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + productFlavors { + } +} + +dependencies { + implementation project(':libs:processing-core') + implementation project(':libs:google-vr') +} \ No newline at end of file diff --git a/libs/processing-vr/proguard-rules.pro b/libs/processing-vr/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/libs/processing-vr/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/libs/processing-vr/src/main/AndroidManifest.xml b/libs/processing-vr/src/main/AndroidManifest.xml new file mode 100755 index 000000000..97330b776 --- /dev/null +++ b/libs/processing-vr/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libs/processing-vr/src/main/java/processing/vr/VRActivity.java b/libs/processing-vr/src/main/java/processing/vr/VRActivity.java new file mode 100644 index 000000000..28e199585 --- /dev/null +++ b/libs/processing-vr/src/main/java/processing/vr/VRActivity.java @@ -0,0 +1,198 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-19 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.vr; + +import android.content.Intent; +import android.util.DisplayMetrics; + +// This will give a "Cannot resolve symbol 'base'" error in Android Studio because it cannot get +// the classes from inside the local .aar files for google-vr. But any VR app runs and can also be debugged. +import com.google.vr.sdk.base.GvrActivity; + +import processing.android.AppComponent; +import processing.android.ServiceEngine; +import processing.core.PApplet; + +public class VRActivity extends GvrActivity implements AppComponent { + static public final int GVR = 3; + + private DisplayMetrics metrics; + private PApplet sketch; + + + public VRActivity() { + + } + + + static public VRGraphics getRenderer(PApplet p) { + return (VRGraphics) p.g; + } + + + public VRActivity(PApplet sketch) { + this.sketch = sketch; + } + + + public void initDimensions() { + metrics = getResources().getDisplayMetrics(); + } + + + public int getDisplayWidth() { + return metrics.widthPixels; + } + + + public int getDisplayHeight() { + return metrics.heightPixels; + } + + + public float getDisplayDensity() { + return metrics.density; + } + + + public int getKind() { + return GVR; + } + + + public void dispose() { + } + + + public void setSketch(PApplet sketch) { + this.sketch = sketch; + if (sketch != null) { + sketch.initSurface(VRActivity.this, null); + // Required to read the paired viewer's distortion parameters. + sketch.requestPermission("android.permission.READ_EXTERNAL_STORAGE"); + } + } + + + public PApplet getSketch() { + return sketch; + } + + + public boolean isService() { + return false; + } + + + public ServiceEngine getEngine() { + return null; + } + + + @Override + public void onResume() { + super.onResume(); + if (sketch != null) { + sketch.onResume(); + } + } + + + @Override + public void onPause() { + super.onPause(); + if (sketch != null) { + sketch.onPause(); + } + } + + + @Override + public void onDestroy() { + super.onDestroy(); + if (sketch != null) { + sketch.onDestroy(); + } + } + + + @Override + public void onStart() { + super.onStart(); + if (sketch != null) { + sketch.onStart(); + } + } + + + @Override + public void onStop() { + super.onStop(); + if (sketch != null) { + sketch.onStop(); + } + } + + + public void requestDraw() { + } + + + public boolean canDraw() { + return true; + } + + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], + int[] grantResults) { + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + + @Override + public void onNewIntent(Intent intent) { + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } +} diff --git a/libs/processing-vr/src/main/java/processing/vr/VRCamera.java b/libs/processing-vr/src/main/java/processing/vr/VRCamera.java new file mode 100644 index 000000000..c7e150297 --- /dev/null +++ b/libs/processing-vr/src/main/java/processing/vr/VRCamera.java @@ -0,0 +1,67 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2019 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.vr; + +import processing.core.PApplet; +import processing.core.PMatrix3D; +import processing.core.PVector; + +public class VRCamera { + protected PApplet parent; + protected VRGraphics graphics; + protected PMatrix3D eyeMat; + + public VRCamera(PApplet parent) { + if (parent.g instanceof VRGraphics) { + this.parent = parent; + this.graphics = (VRGraphics)(parent.g); + } else { + System.err.println("The VR camera can only be created when the VR renderer is in use"); + } + } + + public void sticky() { + parent.pushMatrix(); + parent.eye(); + } + + public void noSticky() { + parent.popMatrix(); + } + + public void setPosition(float x, float y, float z) { + eyeMat = graphics.getEyeMatrix(eyeMat); + float x0 = eyeMat.m03; + float y0 = eyeMat.m13; + float z0 = eyeMat.m23; + graphics.translate(x0 - x, y0 - y, z0 - z); + } + + public void setNear(float near) { + graphics.defCameraNear = near; + } + + public void setFar(float far) { + graphics.defCameraFar = far; + } +} diff --git a/libs/processing-vr/src/main/java/processing/vr/VRGraphics.java b/libs/processing-vr/src/main/java/processing/vr/VRGraphics.java new file mode 100644 index 000000000..f39560457 --- /dev/null +++ b/libs/processing-vr/src/main/java/processing/vr/VRGraphics.java @@ -0,0 +1,264 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-19 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.vr; + +import com.google.vr.sdk.base.Eye; +import com.google.vr.sdk.base.FieldOfView; +import com.google.vr.sdk.base.HeadTransform; +import com.google.vr.sdk.base.Viewport; + +import processing.core.PApplet; +import processing.core.PGraphics; +import processing.core.PMatrix3D; +import processing.core.PVector; +import processing.opengl.PGL; +import processing.opengl.PGLES; +import processing.opengl.PGraphics3D; +import processing.opengl.PGraphicsOpenGL; + +public class VRGraphics extends PGraphics3D { + static public final int LEFT = Eye.Type.LEFT; + static public final int RIGHT = Eye.Type.RIGHT; + static public final int MONOCULAR = Eye.Type.MONOCULAR; + + private boolean initialized = false; + + public HeadTransform headTransform; + public Eye eye; + public int eyeType; + + protected float[] forwardVector; + protected float[] rightVector; + protected float[] upVector; + + private Viewport eyeViewport; + private float[] eyeView; + private float[] eyePerspective; + + + @Override + protected PGL createPGL(PGraphicsOpenGL pg) { + return new PGLES(pg); + } + + + @Override + public void beginDraw() { + super.beginDraw(); + updateView(); + } + + + @Override + public void camera(float eyeX, float eyeY, float eyeZ, + float centerX, float centerY, float centerZ, + float upX, float upY, float upZ) { + PGraphics.showWarning("The camera cannot be set in VR"); + } + + + @Override + public void perspective(float fov, float aspect, float zNear, float zFar) { + PGraphics.showWarning("Perspective cannot be set in VR"); + } + + + @Override + protected void defaultCamera() { + // do nothing + } + + + @Override + protected void defaultPerspective() { + // do nothing + } + + + @Override + protected void saveState() { + } + + + @Override + protected void restoreState() { + } + + + @Override + protected void restoreSurface() { + } + + + protected void updateView() { + setVRViewport(); + setVRCamera(); + setVRProjection(); + } + + + protected void eyeTransform(Eye e) { + eye = e; + eyeType = eye.getType(); + eyeViewport = eye.getViewport(); + eyePerspective = eye.getPerspective(defCameraNear, defCameraFar); + eyeView = eye.getEyeView(); + + // Adjust the camera Z position to it fits the (width,height) rect at Z = 0, given + // the fov settings. + FieldOfView fov = eye.getFov(); + defCameraFOV = fov.getTop()* DEG_TO_RAD; + defCameraZ = (float) (height / (2 * Math.tan(defCameraFOV))); + cameraAspect = (float)width / height; + if (cameraUp) { + defCameraX = 0; + defCameraY = 0; + } else { + defCameraX = +width / 2.0f; + defCameraY = +height / 2.0f; + } + } + + + protected void headTransform(HeadTransform ht) { + initVR(); + + headTransform = ht; + + // Forward, right, and up vectors are given in the original system with Y + // pointing up. Need to invert y coords in the non-gl case: + float yf = cameraUp ? +1 : -1; + + headTransform.getForwardVector(forwardVector, 0); + headTransform.getRightVector(rightVector, 0); + headTransform.getUpVector(upVector, 0); + + forwardX = forwardVector[0]; + forwardY = yf * forwardVector[1]; + forwardZ = forwardVector[2]; + + rightX = rightVector[0]; + rightY = yf * rightVector[1]; + rightZ = rightVector[2]; + + upX = upVector[0]; + upY = yf * upVector[1]; + upZ = upVector[2]; + } + + + protected void initVR() { + if (!initialized) { + forwardVector = new float[3]; + rightVector = new float[3]; + upVector = new float[3]; + initialized = true; + } + } + + + protected void setVRViewport() { + pgl.viewport(eyeViewport.x, eyeViewport.y, eyeViewport.width, eyeViewport.height); + } + + + protected void setVRCamera() { + cameraX = defCameraX; + cameraY = defCameraY; + cameraZ = defCameraZ; + + // Calculating Z vector + float z0 = 0; + float z1 = 0; + float z2 = defCameraZ; + eyeDist = PApplet.abs(z2); + if (nonZero(eyeDist)) { + z0 /= eyeDist; + z1 /= eyeDist; + z2 /= eyeDist; + } + + // Calculating Y vector + float y0 = 0; + float y1 = cameraUp ? + 1: -1; + float y2 = 0; + + // Computing X vector as Y cross Z + float x0 = y1 * z2 - y2 * z1; + float x1 = -y0 * z2 + y2 * z0; + float x2 = y0 * z1 - y1 * z0; + if (!cameraUp) { + // Inverting X axis + x0 *= -1; + x1 *= -1; + x2 *= -1; + } + + // Cross product gives area of parallelogram, which is < 1.0 for + // non-perpendicular unit-length vectors; so normalize x, y here: + float xmag = PApplet.sqrt(x0 * x0 + x1 * x1 + x2 * x2); + if (nonZero(xmag)) { + x0 /= xmag; + x1 /= xmag; + x2 /= xmag; + } + + float ymag = PApplet.sqrt(y0 * y0 + y1 * y1 + y2 * y2); + if (nonZero(ymag)) { + y0 /= ymag; + y1 /= ymag; + y2 /= ymag; + } + + // Pre-apply the eye view matrix: + // https://developers.google.com/vr/android/reference/com/google/vr/sdk/base/Eye.html#getEyeView() + modelview.set(eyeView[0], eyeView[4], eyeView[8], eyeView[12], + eyeView[1], eyeView[5], eyeView[9], eyeView[13], + eyeView[2], eyeView[6], eyeView[10], eyeView[14], + eyeView[3], eyeView[7], eyeView[11], eyeView[15]); + modelview.apply(x0, x1, x2, 0, + y0, y1, y2, 0, + z0, z1, z2, 0, + 0, 0, 0, 1); + float tx = -defCameraX; + float ty = -defCameraY; + float tz = -defCameraZ; + modelview.translate(tx, ty, tz); + + modelviewInv.set(modelview); + modelviewInv.invert(); + + camera.set(modelview); + cameraInv.set(modelviewInv); + } + + + protected void setVRProjection() { + // Matrices in Processing are row-major, and GVR API is column-major + projection.set(eyePerspective[0], eyePerspective[4], eyePerspective[8], eyePerspective[12], + eyePerspective[1], eyePerspective[5], eyePerspective[9], eyePerspective[13], + eyePerspective[2], eyePerspective[6], eyePerspective[10], eyePerspective[14], + eyePerspective[3], eyePerspective[7], eyePerspective[11], eyePerspective[15]); + updateProjmodelview(); + } +} diff --git a/libs/processing-vr/src/main/java/processing/vr/VRGraphicsMono.java b/libs/processing-vr/src/main/java/processing/vr/VRGraphicsMono.java new file mode 100644 index 000000000..6a31a2b57 --- /dev/null +++ b/libs/processing-vr/src/main/java/processing/vr/VRGraphicsMono.java @@ -0,0 +1,35 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-19 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.vr; + +import android.view.SurfaceHolder; +import processing.android.AppComponent; +import processing.core.PSurface; + +public class VRGraphicsMono extends VRGraphics { + @Override + public PSurface createSurface(AppComponent component, SurfaceHolder holder, boolean reset) { // ignore + if (reset) pgl.resetFBOLayer(); + return new VRSurface(this, component, holder, false); + } +} \ No newline at end of file diff --git a/libs/processing-vr/src/main/java/processing/vr/VRGraphicsStereo.java b/libs/processing-vr/src/main/java/processing/vr/VRGraphicsStereo.java new file mode 100644 index 000000000..681de9e1b --- /dev/null +++ b/libs/processing-vr/src/main/java/processing/vr/VRGraphicsStereo.java @@ -0,0 +1,35 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-19 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.vr; + +import android.view.SurfaceHolder; +import processing.android.AppComponent; +import processing.core.PSurface; + +public class VRGraphicsStereo extends VRGraphics { + @Override + public PSurface createSurface(AppComponent component, SurfaceHolder holder, boolean reset) { // ignore + if (reset) pgl.resetFBOLayer(); + return new VRSurface(this, component, holder, true); + } +} diff --git a/libs/processing-vr/src/main/java/processing/vr/VRSurface.java b/libs/processing-vr/src/main/java/processing/vr/VRSurface.java new file mode 100644 index 000000000..dac07ee27 --- /dev/null +++ b/libs/processing-vr/src/main/java/processing/vr/VRSurface.java @@ -0,0 +1,338 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-19 The Processing Foundation + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation, version 2.1. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General + Public License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA +*/ + +package processing.vr; + +import java.io.File; +import java.io.InputStream; + +import javax.microedition.khronos.egl.EGLConfig; + +import com.google.vr.sdk.base.GvrActivity; +import com.google.vr.sdk.base.GvrView; +import com.google.vr.sdk.base.AndroidCompat; +import com.google.vr.sdk.base.Eye; +import com.google.vr.sdk.base.HeadTransform; +import com.google.vr.sdk.base.Viewport; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ConfigurationInfo; +import android.content.res.AssetManager; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import processing.android.AppComponent; +import processing.core.PGraphics; +import processing.opengl.PGLES; +import processing.opengl.PGraphicsOpenGL; +import processing.opengl.PSurfaceGLES; +import android.view.Window; +import android.view.WindowManager; + +public class VRSurface extends PSurfaceGLES { + protected SurfaceViewVR vrView; + protected VRGraphics pvr; + + protected GvrActivity vrActivity; + protected AndroidVRStereoRenderer renderer; + + private boolean needCalculate; + + public VRSurface(PGraphics graphics, AppComponent component, SurfaceHolder holder, boolean vr) { + this.sketch = graphics.parent; + this.graphics = graphics; + this.component = component; + this.pgl = (PGLES)((PGraphicsOpenGL)graphics).pgl; + + vrActivity = (GvrActivity)component; + this.activity = vrActivity; + pvr = (VRGraphics)graphics; + + vrView = new SurfaceViewVR(vrActivity); + + // Enables/disables the transition view used to prompt the user to place + // their phone into a GVR viewer. + vrView.setTransitionViewEnabled(true); + + // Enables Cardboard-trigger feedback with Daydream headsets. This is a simple way of supporting + // Daydream controller input for basic interactions using the existing Cardboard trigger API. + vrView.enableCardboardTriggerEmulation(); + + vrView.setStereoModeEnabled(vr); + if (vr) { + vrView.setDistortionCorrectionEnabled(true); + vrView.setNeckModelEnabled(true); + } + + if (vrView.setAsyncReprojectionEnabled(true)) { + // Async reprojection decouples the app framerate from the display framerate, + // allowing immersive interaction even at the throttled clockrates set by + // sustained performance mode. + AndroidCompat.setSustainedPerformanceMode(vrActivity, true); + } + vrActivity.setGvrView(vrView); + + surfaceView = null; + + // The glview is ready right after creation, does not need to wait for a + // surfaceCreate() event. + surfaceReady = true; + } + + @Override + public Context getContext() { + return vrActivity; + } + + @Override + public Activity getActivity() { + return vrActivity; + } + + @Override + public void finish() { + vrActivity.finish(); + } + + @Override + public AssetManager getAssets() { + return vrActivity.getAssets(); + } + + @Override + public void startActivity(Intent intent) { + vrActivity.startActivity(intent); + } + + @Override + public void initView(int sketchWidth, int sketchHeight) { + Window window = vrActivity.getWindow(); + + // Take up as much area as possible + //requestWindowFeature(Window.FEATURE_NO_TITLE); // may need to set in theme properties + // the above line does not seem to be needed when using VR + // android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen" > + window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + + // This does the actual full screen work + window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + window.setContentView(vrView); + } + + @Override + public String getName() { + return vrActivity.getComponentName().getPackageName(); + } + + @Override + public void setOrientation(int which) { + PGraphics.showWarning("Orientation in VR apps cannot be changed"); + } + + @Override + public File getFilesDir() { + return vrActivity.getFilesDir(); + } + + @Override + public InputStream openFileInput(String filename) { + return null; + } + + @Override + public File getFileStreamPath(String path) { + return vrActivity.getFileStreamPath(path); + } + + @Override + public void dispose() { +// surface.onDestroy(); + } + + + /////////////////////////////////////////////////////////// + + // Thread handling + + private boolean running = false; + + @Override + public void startThread() { + vrView.onResume(); + running = true; + } + + @Override + public void pauseThread() { + vrView.onPause(); + running = false; + } + + @Override + public void resumeThread() { + vrView.onResume(); + running = true; + } + + @Override + public boolean stopThread() { + running = false; + return true; + } + + @Override + public boolean isStopped() { + return !running; + } + + /////////////////////////////////////////////////////////// + + public class SurfaceViewVR extends GvrView { + public SurfaceViewVR(Context context) { + super(context); + + // Check if the system supports OpenGL ES 2.0. + final ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + final ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo(); + final boolean supportsGLES2 = configurationInfo.reqGlEsVersion >= 0x20000; + + if (!supportsGLES2) { + throw new RuntimeException("OpenGL ES 2.0 is not supported by this device."); + } + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + + + int samples = sketch.sketchSmooth(); + if (1 < samples) { + setMultisampling(samples); + } else { + // use default EGL config chooser for now +// setEGLConfigChooser(8, 8, 8, 8, 16, 8); + } + + // The renderer can be set only once. + setRenderer(getVRStereoRenderer()); + } + + + @Override + public boolean onTouchEvent(MotionEvent event) { + return sketch.surfaceTouchEvent(event); + } + + + @Override + public boolean onKeyDown(int code, android.view.KeyEvent event) { + sketch.surfaceKeyDown(code, event); + return super.onKeyDown(code, event); + } + + + @Override + public boolean onKeyUp(int code, android.view.KeyEvent event) { + sketch.surfaceKeyUp(code, event); + return super.onKeyUp(code, event); + } + } + + /////////////////////////////////////////////////////////// + + // Android specific classes (Renderer, ConfigChooser) + + + public AndroidVRStereoRenderer getVRStereoRenderer() { + renderer = new AndroidVRStereoRenderer(); + return renderer; + } + + + protected class AndroidVRStereoRenderer implements GvrView.StereoRenderer { + public AndroidVRStereoRenderer() { + + } + + @Override + public void onNewFrame(HeadTransform transform) { + hadnleGVREnumError(); + pgl.getGL(null); + pvr.headTransform(transform); + needCalculate = true; + } + + @Override + public void onDrawEye(Eye eye) { + pvr.eyeTransform(eye); + if (needCalculate) { + // Call calculate() right after we have the first eye transform. + // This allows to update the modelview and projection matrices, so + // geometry-related calculations can also be conducted in calculate(). + pvr.updateView(); + sketch.calculate(); + needCalculate = false; + } + sketch.handleDraw(); + } + + @Override + public void onFinishFrame(Viewport arg0) { + } + + @Override + public void onRendererShutdown() { + } + + @Override + public void onSurfaceChanged(int iwidth, int iheight) { + sketch.surfaceChanged(); + graphics.surfaceChanged(); + + sketch.setSize(iwidth, iheight); + graphics.setSize(sketch.sketchWidth(), sketch.sketchHeight()); + } + + @Override + public void onSurfaceCreated(EGLConfig arg0) { + } + + // Don't print the invalid enum error: + // https://github.com/processing/processing-android/issues/281 + // seems harmless as it happens in the first frame only + // TODO: need to find the reason for the error (gl config?) + private void hadnleGVREnumError() { + int err = pgl.getError(); + if (err != 0 && err != 1280) { + String where = "top onNewFrame"; + String errString = pgl.errorString(err); + String msg = "OpenGL error " + err + " at " + where + ": " + errString; + PGraphics.showWarning(msg); + } + } + } +} diff --git a/mode.properties b/mode.properties deleted file mode 100644 index 98b6b8f0a..000000000 --- a/mode.properties +++ /dev/null @@ -1,9 +0,0 @@ -name = Android Mode -authorList = [The Processing Foundation](http://android.processing.org/) -url = https://github.com/processing/processing-android -sentence = Create projects with Processing for Android devices -paragraph = This version of the Android Mode is for Processing 3.0+ -version = 232 -prettyVersion = 3.0.1 -minRevision = 228 -maxRevision = 0 diff --git a/mode/.gitignore b/mode/.gitignore deleted file mode 100644 index 7e0d4cfbd..000000000 --- a/mode/.gitignore +++ /dev/null @@ -1 +0,0 @@ -AndroidMode.jar diff --git a/mode/org.eclipse.core.runtime-3.1.0.jar b/mode/org.eclipse.core.runtime-3.1.0.jar deleted file mode 100644 index 746421bbc..000000000 Binary files a/mode/org.eclipse.core.runtime-3.1.0.jar and /dev/null differ diff --git a/processing-android b/processing-android deleted file mode 100644 index 5bf102cad..000000000 --- a/processing-android +++ /dev/null @@ -1,117 +0,0 @@ -#!/bin/sh - -# This script runs Processing, using the JDK in the Processing -# installation directory if possible. - -# If no JDK was installed with Processing then the script tries to use -# the preferred Java version of the machine, i.e. what is executed -# by the "java" console command. This must be a Sun JDK (for details, see -# http://processing.org/reference/environment/platforms.html#java). - -# In order to run Processing with an already installed JDK that is *not* -# the preferred Java version of the machine, create a symlink named "java" -# in the Processing installation directory that points to the JDK home -# directory. - -# Thanks to Ferdinand Kasper for this build script. [fry] - - -# JARs required from JDK (anywhere in/below the JDK home directory) -JDKLIBS="rt.jar tools.jar" - -# Set this to non-zero for logging -LOGGING=0 - -# Logs name and value of a variable to stdout if LOGGING is non-zero. -# Expects the variable name as parameter $1. -log() { - if [ $LOGGING -ne 0 ]; then - eval echo $1=\$$1 - fi -} - - -# Locates JDKLIBS in a directory and its subdirectories and saves their -# absolute paths as list to JDKCP. Expects the directory as parameter $1. -# Sets SUCCESS to 1 if all libraries were found, to 0 otherwise. -make_jdkcp() { - # Back out of JRE directory if apparently located inside a JDK - if [ -f "$1/../bin/java" ]; then - DIR="$1/.." - else - DIR="$1" - fi - log DIR - - JDKCP= - SUCCESS=1 - - # Locate JDKLIBS - for L in $JDKLIBS; do - # Locate only the first library with a matching name - LIB=`find "$DIR" -name $L 2>/dev/null | head -n 1` - log L - log LIB - - # Library found? - if [ -n "$LIB" ]; then - JDKCP="$JDKCP"${JDKCP:+:}"$LIB" - else - SUCCESS=0 - fi - done - - log JDKCP -} - - -# Get absolute path of directory where this script is located -APPDIR=`readlink -f "$0"` -APPDIR=`dirname "$APPDIR"` -log APPDIR - -# Try using a local JDK from the same directory as this script -JDKDIR=`readlink -f "$APPDIR/java"` -make_jdkcp "$JDKDIR" -log SUCCESS - -# Local JDK found? -if [ $SUCCESS -ne 1 ]; then - # No, try using the preferred system JRE/JDK (if any) - JDKDIR=`which java` && JDKDIR=`readlink -e "$JDKDIR"` && JDKDIR=`dirname "$JDKDIR"`/.. - make_jdkcp "$JDKDIR" - log SUCCESS -fi - -# Add all required JARs to CLASSPATH -CLASSPATH="$CLASSPATH"${CLASSPATH:+:}"$JDKCP" -for LIB in "$APPDIR"/lib/*.jar; do - CLASSPATH="$CLASSPATH"${CLASSPATH:+:}"$LIB" -done -for LIB in "$APPDIR"/core/library/*.jar; do - CLASSPATH="$CLASSPATH"${CLASSPATH:+:}"$LIB" -done -export CLASSPATH -log CLASSPATH - -# Make all JDK binaries available in PATH -export PATH="$JDKDIR/bin":"$PATH" -log PATH - -current_name=`basename $0` -cmd_name='processing-android' - -if [ $current_name = $cmd_name ] -then - java processing.mode.android.Commander "$@" -else - # Start Processing in the same directory as this script - if [ "$1" ]; then - SKETCH=`readlink -f $1` - else - SKETCH= - fi - cd "$APPDIR" - - java processing.app.Base "$SKETCH" & -fi diff --git a/processing/.gitignore b/processing/.gitignore new file mode 100644 index 000000000..9c6eb9d40 --- /dev/null +++ b/processing/.gitignore @@ -0,0 +1,14 @@ +mode/processing-core.zip +mode/mode/AndroidMode.jar +mode/mode/gradle-tooling-api* +mode/mode/slf4j* + +mode/mode/percent.jar +mode/mode/recyclerview-v7.jar +mode/mode/support-* +mode/mode/wearable.jar + +mode/libraries/vr/library +mode/libraries/ar/library +mode/tools/SDKUpdater/tool +mode/tools/SDKUpdater/lib diff --git a/processing/README.md b/processing/README.md new file mode 100644 index 000000000..743c8422c --- /dev/null +++ b/processing/README.md @@ -0,0 +1,6 @@ +Processing for Android +====================== + +This is the main repository for Processing for Android. It includes the core library inside the core folder, and the mode itself in the root. See the [wiki](https://github.com/processing/processing-android/wiki) for build instructions. + + diff --git a/processing/build.gradle b/processing/build.gradle new file mode 100644 index 000000000..9823664a5 --- /dev/null +++ b/processing/build.gradle @@ -0,0 +1,175 @@ +import java.nio.file.Files +import org.zeroturnaround.zip.ZipUtil +import org.apache.commons.io.FileUtils +import java.util.regex.Pattern +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.3.1' + classpath group: 'commons-io', name: 'commons-io', version: '2.12.0' + classpath group: 'org.zeroturnaround', name: 'zt-zip', version: '1.15' + } +} + +plugins { + id 'java' + id('io.github.gradle-nexus.publish-plugin') version '1.1.0' +} + +apply from: "${rootDir}/scripts/publish-root.gradle" + +allprojects { + apply plugin: 'java' + apply plugin: 'java-library' + + Properties versions = new Properties() + versions.load(project.rootProject.file("mode/version.properties").newDataInputStream()) + ext.targetSdkVersion = versions.getProperty("android-platform") + ext.appcompatVersion = versions.getProperty("androidx.appcompat%appcompat") + ext.v4legacyVersion = versions.getProperty("androidx.legacy%legacy-support-v4") + ext.wearVersion = versions.getProperty("com.google.android.support%wearable") + ext.gvrVersion = versions.getProperty("com.google.vr") + ext.garVersion = versions.getProperty("com.google.ar") + ext.processingVersion = versions.getProperty("org.processing") + ext.toolingVersion = versions.getProperty("org.gradle%gradle-tooling-api") + ext.slf4jVersion = versions.getProperty("org.slf4j") + ext.gradlewVersion = versions.getProperty("gradle-wrapper") + ext.toolsLibVersion = versions.getProperty("android-toolslib") + ext.jdtVersion = versions.getProperty("org.eclipse.jdt") + + Properties modeProperties = new Properties() + modeProperties.load(project.rootProject.file("mode/mode.properties").newDataInputStream()) + ext.modeVersion = modeProperties.getProperty("prettyVersion") + + Properties vrProperties = new Properties() + vrProperties.load(project.rootProject.file("mode/libraries/vr/library.properties").newDataInputStream()) + ext.vrLibVersion = vrProperties.getProperty("prettyVersion") + + Properties arProperties = new Properties() + arProperties.load(project.rootProject.file("mode/libraries/ar/library.properties").newDataInputStream()) + ext.arLibVersion = arProperties.getProperty("prettyVersion") + + + def fn = project.rootProject.file("local.properties") + if (!fn.exists()) { + if (System.env["ANDROID_SDK"] != null) { + def syspath = System.env["ANDROID_SDK"] + def parts = syspath.split(Pattern.quote(File.separator)) + def path = String.join("/", parts) + fn.withWriterAppend { w -> + w << "sdk.dir=${path}\n" + } + } else { + throw new GradleException( + "The file local.properties does not exist, and there is no ANDROID_SDK environmental variable defined in the system.\n" + + "Define ANDROID_SDK so it points to the location of the Android SDK, or create the local.properties file manually\n" + + "and add the following line to it:\n" + + "sdk.dir=") + } + } + + + Properties localProperties = new Properties() + localProperties.load(project.rootProject.file("local.properties").newDataInputStream()) + def sdkDir = localProperties.getProperty("sdk.dir") + ext.androidPlatformPath = "${sdkDir}/platforms/android-${targetSdkVersion}" + ext.coreZipPath = "${rootDir}/mode/processing-core.zip" + + repositories { + google() + mavenCentral() + maven { url "https://maven.google.com" } + maven { url "https://jitpack.io" } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + flatDir dirs: androidPlatformPath + flatDir dirs: "${rootDir}/core/build/libs" + } + + compileJava { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + + // Uncomment this option when building with Java 11+ + // https://github.com/processing/processing-android/issues/625 + // options.release = 8 + } +} + +clean.doFirst { + delete "dist" +} + +task dist { + dependsOn subprojects.build + doLast { + def root = "${buildDir}/zip/AndroidMode" + + // Copy assets to build dir + FileUtils.copyDirectory(file("mode/templates"), file("${root}/templates")) + FileUtils.copyDirectory(file("mode/examples"), file("${root}/examples")) + FileUtils.copyDirectory(file("mode/icons"), file("${root}/icons")) + FileUtils.copyDirectory(file("mode/theme"), file("${root}/theme")) + FileUtils.copyDirectory(file("mode/mode"), file("${root}/mode")) + delete "${root}/mode/core.jar" + delete "${root}/mode/pde.jar" + delete "${root}/mode/JavaMode.jar" + // delete "${root}/mode/jdi.jar" + // delete "${root}/mode/jdimodel.jar" + + Files.copy(file("mode/processing-core.zip").toPath(), + file("${root}/processing-core.zip").toPath(), REPLACE_EXISTING) + + Files.copy(file("mode/keywords.txt").toPath(), + file("${root}/keywords.txt").toPath(), REPLACE_EXISTING) + + Files.copy(file("mode/version.properties").toPath(), + file("${root}/version.properties").toPath(), REPLACE_EXISTING) + + Files.copy(file("mode/mode.properties").toPath(), + file("${root}/mode.properties").toPath(), REPLACE_EXISTING) + + FileUtils.copyDirectory(file("mode/languages"), + file("${root}/languages")) + + FileUtils.copyDirectory(file("mode/resources"), + file("${root}/resources")) + + FileUtils.copyDirectory(file("mode/tools/SDKUpdater/tool"), + file("${root}/tools/SDKUpdater/tool")) + FileUtils.copyDirectory(file("mode/tools/SDKUpdater/lib"), + file("${root}/tools/SDKUpdater/lib")) + FileUtils.copyDirectory(file("mode/tools/SDKUpdater/src"), + file("${root}/tools/SDKUpdater/src")) + + FileUtils.copyDirectory(file("mode/libraries/vr/examples"), + file("${root}/libraries/vr/examples")) + FileUtils.copyDirectory(file("mode/libraries/vr/library"), + file("${root}/libraries/vr/library")) + FileUtils.copyDirectory(file("mode/libraries/vr/libs"), + file("${root}/libraries/vr/libs")) + FileUtils.copyDirectory(file("../libs/processing-vr/src/main/java/"), + file("${root}/libraries/vr/src")) + Files.copy(file("mode/libraries/vr/library.properties").toPath(), + file("${root}/libraries/vr/library.properties").toPath(), REPLACE_EXISTING) + + FileUtils.copyDirectory(file("mode/libraries/ar/examples"), + file("${root}/libraries/ar/examples")) + FileUtils.copyDirectory(file("mode/libraries/ar/library"), + file("${root}/libraries/ar/library")) + FileUtils.copyDirectory(file("../libs/processing-ar/src/main/java/"), + file("${root}/libraries/ar/src")) + Files.copy(file("mode/libraries/ar/library.properties").toPath(), + file("${root}/libraries/ar/library.properties").toPath(), REPLACE_EXISTING) + + File distFolder = file("dist") + distFolder.mkdirs() + ZipUtil.pack(file("${buildDir}/zip"), new File("dist/AndroidMode.zip")) + Files.copy(file("mode/mode.properties").toPath(), + file("dist/AndroidMode.txt").toPath(), REPLACE_EXISTING) + } +} diff --git a/processing/buildSrc/build.gradle b/processing/buildSrc/build.gradle new file mode 100644 index 000000000..e6ffdbeb9 --- /dev/null +++ b/processing/buildSrc/build.gradle @@ -0,0 +1,17 @@ +// apply plugin: 'groovy' +plugins { + id 'groovy' +} + +repositories { + google() + mavenCentral() +} + +dependencies { + implementation gradleApi() + implementation localGroovy() + implementation 'com.android.tools.build:gradle:7.3.1' + implementation 'com.google.guava:guava:32.0.0-jre' + implementation 'com.android.tools:common:25.3.0' +} \ No newline at end of file diff --git a/processing/buildSrc/src/main/groovy/ImportAar.groovy b/processing/buildSrc/src/main/groovy/ImportAar.groovy new file mode 100644 index 000000000..fc20588f1 --- /dev/null +++ b/processing/buildSrc/src/main/groovy/ImportAar.groovy @@ -0,0 +1,237 @@ +// import org.gradle.api.Plugin +// import org.gradle.api.Project +// import org.gradle.api.artifacts.Configuration +// import org.gradle.api.artifacts.transform.TransformAction +// import org.gradle.api.artifacts.transform.TransformParameters +// import org.gradle.api.artifacts.transform.InputArtifact +// import org.gradle.api.artifacts.transform.TransformOutputs +// import org.gradle.api.file.FileSystemLocation +// import org.gradle.api.provider.Provider +// import org.gradle.api.tasks.PathSensitive +// import org.gradle.api.tasks.PathSensitivity + +// import org.gradle.api.attributes.LibraryElements +// import org.gradle.api.attributes.Usage +// import org.gradle.api.attributes.Category + +// import com.android.build.gradle.internal.dependency.AarTransform +// import com.android.build.gradle.internal.dependency.ExtractAarTransform +// import com.android.build.gradle.internal.publishing.AndroidArtifacts +// import com.android.builder.aar.AarExtractor +// import com.google.common.collect.ImmutableList + +// import java.nio.file.Files +// import static java.nio.file.StandardCopyOption.REPLACE_EXISTING + + +// import org.gradle.api.Plugin +// import org.gradle.api.Project +// import org.gradle.api.artifacts.Configuration +// import org.gradle.api.artifacts.ResolvedArtifact +// import java.util.zip.ZipFile + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.TaskAction +import java.util.zip.ZipFile + +/** + * Build Gradle plgin needed to use aar files as dependencies in a pure java library project. + * Adapted from the following plugin by nekocode + * https://github.com/nekocode/Gradle-Import-Aar + * Ported to Groovy, and made specific to the needs of the Android mode build process (i.e.: this plugin + * is not meant to be used with other projects). + * Ported to Gradle 8 replacing the deprecated ArtifactTransform with the new TransformAction API. + */ +class ImportAar implements Plugin { + final String CONFIG_NAME_POSTFIX = "Aar" + + @Override + void apply(Project project) { + // def aar = AndroidArtifacts.TYPE_AAR + // def jar = AndroidArtifacts.TYPE_JAR + + println ">>> Calling ImportAar" + + // Create a custom resolvable configuration + project.configurations.create('aarExtractorResolvable') { + canBeResolved = true + canBeConsumed = false + extendsFrom project.configurations.implementation + } + + project.tasks.register('extractAarJars', ExtractAarJarsTask) { + group = 'build' + description = 'Extracts JAR files from AAR dependencies and places them in build/libs.' + } + + + // project.task('extractAarJars') { + // doLast { + // println "=======> Calling extractAarJars task" + + // project.configurations.each { Configuration config -> + // config.resolvedConfiguration.resolvedArtifacts.each { ResolvedArtifact artifact -> + // println "Resolved artifact: ${artifact}" + // if (artifact.type == 'aar') { + // extractJarFromAar(artifact, project) + // } + // } + // } + // } + // } + + + +/* + // Create AAR configurations + Collection allConfigs = project.getConfigurations().toList() + for (Configuration config: allConfigs) { + println config + Configuration aarConfig = project.configurations.maybeCreate(config.name + CONFIG_NAME_POSTFIX) + println aarConfig + + // Add extracted jars to original configuration after project evaluating + aarConfig.attributes { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements, LibraryElements.JAR)) + attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category, Category.LIBRARY)) + } + + project.afterEvaluate { + println "-> In afterEvaluate" + aarConfig.resolvedConfiguration.resolvedArtifacts.each { artifact -> + File jarFile = artifact.file + print "================================================> FILE " + println jarFile + println jarFile.getName() + + // Add jar file to classpath + project.sourceSets.main.compileClasspath += project.files(jarFile) + + File libraryFolder = new File(project.buildDir, "libs") + libraryFolder.mkdirs() + + // Strip version number when copying + String name = jarFile.name + int p = name.lastIndexOf("-") + String libName = name.substring(0, p) + ".jar" + File libraryJar = new File(libraryFolder, libName) + Files.copy(jarFile.toPath(), libraryJar.toPath(), REPLACE_EXISTING) + } + } + } + + // Register aar transform + project.dependencies { + registerTransform(AarToJarTransform) { + from.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements, aar)) + to.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements, jar)) + } + } + */ + } + + // void extractJarFromAar(ResolvedArtifact artifact, Project project) { + // println "Input AAR: ${aarFile}" + // def aarFile = artifact.file + // def zipFile = new ZipFile(aarFile) + // def entry = zipFile.getEntry('classes.jar') + // if (entry) { + // project.copy { + // from project.zipTree(aarFile) + // include 'classes.jar' + // into "${project.buildDir}/libs" + // rename { "${artifact.name}-${artifact.moduleVersion.id.version}.jar" } + // } + // } + // zipFile.close() + // } + + + // abstract static class AarToJarTransform implements TransformAction { + // AarToJarTransform() { + // println "AarToJarTransform instantiated" + // } + + // @InputArtifact + // @PathSensitive(PathSensitivity.NAME_ONLY) + // abstract Provider getInputArtifact() + + // @Override + // void transform(TransformOutputs outputs) { + // File inputFile = inputArtifact.get().asFile + // println "Input AAR: ${inputFile}" + // File explodedDir = new File(outputs.getOutputDirectory(), "exploded") + // println "Exploded Directory: ${explodedDir}" + + // AarExtractor aarExtractor = new AarExtractor() + // aarExtractor.extract(inputFile, explodedDir) + // File classesJar = new File(new File(explodedDir, "jars"), "classes.jar") + // if (classesJar.exists()) { + // println "Classes JAR found: ${classesJar}" + // String aarName = inputFile.name.replace(".aar", "") + // File renamedJar = outputs.file("${aarName}.jar") + // Files.copy(classesJar.toPath(), renamedJar.toPath(), REPLACE_EXISTING) + // println "Transformed JAR: ${renamedJar}" + // } else { + // println "Error: classes.jar not found in ${explodedDir}" + // } + // } + // } +} + + +class ExtractAarJarsTask extends org.gradle.api.DefaultTask { + @TaskAction + void extractJars() { + //File outputDir = new File(project.buildDir, 'libs') + File outputDir = new File(System.getProperty("user.dir"), "build/libs") + outputDir.mkdirs() + + // Configuration compileClasspath = project.configurations.getByName('implementation') + Configuration aarExtractorResolvable = project.configurations.getByName('aarExtractorResolvable') + + // compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> + aarExtractorResolvable.resolvedConfiguration.resolvedArtifacts.each { artifact -> + if (artifact.type == 'aar') { + File aarFile = artifact.file + println "Processing AAR: ${aarFile.name}" + + // Extract the AAR file + ZipFile zipFile = new ZipFile(aarFile) + zipFile.entries().each { entry -> + if (entry.name.endsWith('.jar')) { + println "Classes JAR found: ${entry}" + String aarName = aarFile.name.replace(".aar", "") + String jarName = "${aarName}.jar".replaceFirst(/-\d+(\.\d+)*(?=\.jar$)/, '') + + File jarOutput = new File(outputDir, jarName) + jarOutput.parentFile.mkdirs() + + // Write the JAR file to the output directory + zipFile.getInputStream(entry).withCloseable { inputStream -> + jarOutput.withOutputStream { outputStream -> + copyStream(inputStream, outputStream) + } + } + println "Extracted JAR: ${jarOutput.absolutePath}" + } + } + } + } + } + + /** + * Copies data from an InputStream to an OutputStream. + */ + void copyStream(InputStream input, OutputStream output) { + byte[] buffer = new byte[1024] + int bytesRead + while ((bytesRead = input.read(buffer)) != -1) { + output.write(buffer, 0, bytesRead) + } + } +} + diff --git a/processing/buildSrc/src/main/resources/META-INF/gradle-plugins/ImportAar.properties b/processing/buildSrc/src/main/resources/META-INF/gradle-plugins/ImportAar.properties new file mode 100644 index 000000000..f41e15936 --- /dev/null +++ b/processing/buildSrc/src/main/resources/META-INF/gradle-plugins/ImportAar.properties @@ -0,0 +1 @@ +implementation-class=ImportAar \ No newline at end of file diff --git a/processing/core/build.gradle b/processing/core/build.gradle new file mode 100644 index 000000000..07a8b8b73 --- /dev/null +++ b/processing/core/build.gradle @@ -0,0 +1,110 @@ +import org.apache.tools.ant.Project +import java.nio.file.Files +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +plugins { + id 'ImportAar' + id 'java-library' + id 'maven-publish' +} + +dependencies { + implementation name: "android" + implementation "androidx.legacy:legacy-support-v4:${v4legacyVersion}" + implementation "com.google.android.support:wearable:${wearVersion}" +} + +sourceSets.main { + java.srcDir("../../libs/processing-core/src/main/java/") + resources.srcDir("../../libs/processing-core/src/main/") + resources.exclude("AndroidManifest.xml", "**/java/**") +} + +tasks.register('sourceJar', Jar) { + dependsOn classes + duplicatesStrategy = DuplicatesStrategy.INCLUDE + archiveClassifier.set("sources") + from sourceSets.main.allSource +} + +// Does not work because of Processing-specific tags in source code, such as @webref +tasks.register('javadocJar', Jar) { + dependsOn javadoc + archiveClassifier.set("javadoc") + from javadoc.destinationDir +} + +// project.afterEvaluate { +// tasks.named('extractAarJars').configure { +// dependsOn configurations.runtimeClasspath +// } +// } + +// project.tasks.named('build').configure { +// finalizedBy('extractAarJars') +// } + +artifacts { +// archives javadocJar + archives sourceJar +} + +jar.doLast { task -> + ant.checksum file: task.archiveFile.get().asFile +} + +tasks.named('clean').configure { + doFirst { + delete "dist" + delete "${coreZipPath}" + } +} + +tasks.named('compileJava').configure { + doFirst { + String[] deps = ["wearable.jar"] + deps.each { fn -> + Files.copy(file("${rootDir}/build/libs/${fn}").toPath(), + file("${rootDir}/mode/mode/${fn}").toPath(), REPLACE_EXISTING) + } + } +} + +tasks.named('build').configure { + doLast { + // Need to check the existance of the files before using as the files + // will get generated only if Task :core:jar is not being skipped + // Task :core:jar will be skipped if source files are unchanged or jar task is UP-TO-DATE + if (file("${buildDir}/libs/core.jar").exists()) { + // Copying core jar as zip inside the mode folder + Files.copy(file("${buildDir}/libs/core.jar").toPath(), + file("${coreZipPath}").toPath(), REPLACE_EXISTING) + } + // Renaming artifacts for maven publishing + if (file("${buildDir}/libs/core.jar").exists()) { + Files.move(file("${buildDir}/libs/core.jar").toPath(), + file("$buildDir/libs/processing-core-${modeVersion}.jar").toPath(), REPLACE_EXISTING) + } + if (file("${buildDir}/libs/core-sources.jar").exists()) { + Files.move(file("${buildDir}/libs/core-sources.jar").toPath(), + file("$buildDir/libs/processing-core-${modeVersion}-sources.jar").toPath(), REPLACE_EXISTING) + } + if (file("${buildDir}/libs/core.jar.MD5").exists()) { + Files.move(file("${buildDir}/libs/core.jar.MD5").toPath(), + file("$buildDir/libs/processing-core-${modeVersion}.jar.md5").toPath(), REPLACE_EXISTING) + } + } +} + +ext { + libName = 'processing-core' + libVersion = modeVersion + libJar = "${buildDir}/libs/${libName}-${libVersion}.jar" + libSrc = "${buildDir}/libs/${libName}-${libVersion}-sources.jar" + libMd5 = "${buildDir}/libs/${libName}-${libVersion}-sources.jar.md5" + libDependencies = [[name: 'legacy-support-v4', group: 'androidx.legacy', version: v4legacyVersion], + [name: 'wearable', group: 'com.google.android.support', version: wearVersion], + [name: 'android']] +} + +apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/processing/gradle.properties b/processing/gradle.properties new file mode 100644 index 000000000..5465fec0e --- /dev/null +++ b/processing/gradle.properties @@ -0,0 +1,2 @@ +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/processing/gradle/wrapper/gradle-wrapper.jar b/processing/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..41d9927a4 Binary files /dev/null and b/processing/gradle/wrapper/gradle-wrapper.jar differ diff --git a/processing/gradle/wrapper/gradle-wrapper.properties b/processing/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..d6e308a63 --- /dev/null +++ b/processing/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/processing/gradlew b/processing/gradlew new file mode 100755 index 000000000..1b6c78733 --- /dev/null +++ b/processing/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/processing/gradlew.bat b/processing/gradlew.bat new file mode 100644 index 000000000..ac1b06f93 --- /dev/null +++ b/processing/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/processing/mode/.classpath b/processing/mode/.classpath new file mode 100644 index 000000000..3242dff64 --- /dev/null +++ b/processing/mode/.classpath @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/processing/mode/.project b/processing/mode/.project new file mode 100644 index 000000000..e89bdfe07 --- /dev/null +++ b/processing/mode/.project @@ -0,0 +1,34 @@ + + + android-mode + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1675640664215 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/processing/mode/.settings/org.eclipse.buildship.core.prefs b/processing/mode/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..e8895216f --- /dev/null +++ b/processing/mode/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/processing/mode/.settings/org.eclipse.jdt.core.prefs b/processing/mode/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0fee6a9c4 --- /dev/null +++ b/processing/mode/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,15 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/processing/mode/build.gradle b/processing/mode/build.gradle new file mode 100644 index 000000000..0f34a95d9 --- /dev/null +++ b/processing/mode/build.gradle @@ -0,0 +1,102 @@ +import java.nio.file.Files +import org.zeroturnaround.zip.ZipUtil +import org.apache.commons.io.FileUtils +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +plugins { + id 'java' +} + +// Extend compile to copy the jars from gradle-tooling and slf4j: +// https://stackoverflow.com/a/43602463 +configurations { + implementationCopy + implementationExtract +} + +dependencies { + // implementation group: "org.processing", name: "core", version: "${processingVersion}" + // implementation group: "org.processing", name: "pde", version: "${processingVersion}" + // implementation group: "org.processing", name: "java-mode", version: "${processingVersion}" + + implementationExtract "org.eclipse.jdt:org.eclipse.jdt.debug:${jdtVersion}" + + implementationCopy "org.gradle:gradle-tooling-api:${toolingVersion}" + implementationCopy "org.slf4j:slf4j-api:${slf4jVersion}" + implementationCopy "org.slf4j:slf4j-simple:${slf4jVersion}" + + implementation fileTree(include: ["jdi.jar", "jdimodel.jar", "core.jar", "pde.jar", "JavaMode.jar"], dir: 'mode') +} + +// This task copies the gradle tooling jar into the mode folder +tasks.register("copyToLib", Copy) { + from(configurations.implementationCopy) + into("mode") +} + +tasks.named('build') { + dependsOn 'copyToLib' +} + +tasks.named('compileJava') { + dependsOn 'copyToLib' +} + +sourceSets.main.java.srcDir("src/") + +tasks.register('getjdi', Copy) { + // This task extracts the jar files inside org.eclipse.jdt.debug, which are + // jdi.jar and jdimodel.jar and needed to build the debugger. + from(zipTree(configurations.implementationExtract.singleFile)) { + include '**/*.jar' + exclude 'META-INF' + } + into "mode" +} + +tasks.register('permissions', Exec) { + // This task retrieves the latest list of Android permissions and adds them + // to the Permissions.java file. The python scripts requries BeautifulSoup + workingDir "scripts" + commandLine "python", "permissions.py" +} + + +tasks.register("wrapper", Wrapper) { + gradleVersion = "${gradlewVersion}" // version required for gradle wrapper +} + +tasks.named("wrapper").configure { + doLast { + def wrapperFolder = file("mode/gradlew") + wrapperFolder.mkdirs() + file("gradle").renameTo(file("mode/gradlew/gradle")) + file("gradlew").renameTo(file("mode/gradlew/gradlew")) + file("gradlew.bat").renameTo(file("mode/gradlew/gradlew.bat")) + FileUtils.copyDirectory(file("gradle"), file("../debug/gradle")) + delete("gradle") + ZipUtil.pack(file("mode/gradlew"), new File("mode/mode/gradlew.zip")) + delete("mode/gradlew") + } +} + +tasks.named('clean') { + doFirst { + delete fileTree("mode") { + include "**/*.jar" + exclude "jdi.jar", "jdimodel.jar", "istack-commons-runtime.jar", "javax.activation-api.jar", + "jaxb-api.jar", "jaxb-jxc.jar", "jaxb-runtime.jar", "jaxb-xjc.jar", "core.jar", + "pde.jar", "JavaMode.jar", "org.eclipse.core.contenttype.jar", "org.eclipse.core.jobs.jar", + "org.eclipse.core.resources.jar", "org.eclipse.core.runtime.jar", "org.eclipse.equinox.common.jar", + "org.eclipse.equinox.preferences.jar", "org.eclipse.jdt.core.jar", "org.eclipse.osgi.jar", + "org.eclipse.text.jar" + } + } +} + +tasks.named('build') { + doLast { + Files.copy(file("$buildDir/libs/mode.jar").toPath(), + file("mode/AndroidMode.jar").toPath(), REPLACE_EXISTING) + } +} \ No newline at end of file diff --git a/examples/Basics/Arrays/Array/Array.pde b/processing/mode/examples/Basics/Arrays/Array/Array.pde similarity index 100% rename from examples/Basics/Arrays/Array/Array.pde rename to processing/mode/examples/Basics/Arrays/Array/Array.pde diff --git a/examples/Basics/Arrays/Array2D/Array2D.pde b/processing/mode/examples/Basics/Arrays/Array2D/Array2D.pde similarity index 100% rename from examples/Basics/Arrays/Array2D/Array2D.pde rename to processing/mode/examples/Basics/Arrays/Array2D/Array2D.pde diff --git a/examples/Basics/Arrays/ArrayObjects/ArrayObjects.pde b/processing/mode/examples/Basics/Arrays/ArrayObjects/ArrayObjects.pde similarity index 100% rename from examples/Basics/Arrays/ArrayObjects/ArrayObjects.pde rename to processing/mode/examples/Basics/Arrays/ArrayObjects/ArrayObjects.pde diff --git a/examples/Basics/Arrays/ArrayObjects/Module.pde b/processing/mode/examples/Basics/Arrays/ArrayObjects/Module.pde similarity index 100% rename from examples/Basics/Arrays/ArrayObjects/Module.pde rename to processing/mode/examples/Basics/Arrays/ArrayObjects/Module.pde diff --git a/examples/Basics/Camera/MoveEye/MoveEye.pde b/processing/mode/examples/Basics/Camera/MoveEye/MoveEye.pde similarity index 100% rename from examples/Basics/Camera/MoveEye/MoveEye.pde rename to processing/mode/examples/Basics/Camera/MoveEye/MoveEye.pde diff --git a/examples/Basics/Camera/Perspective/Perspective.pde b/processing/mode/examples/Basics/Camera/Perspective/Perspective.pde similarity index 100% rename from examples/Basics/Camera/Perspective/Perspective.pde rename to processing/mode/examples/Basics/Camera/Perspective/Perspective.pde diff --git a/examples/Basics/Color/Brightness/Brightness.pde b/processing/mode/examples/Basics/Color/Brightness/Brightness.pde similarity index 100% rename from examples/Basics/Color/Brightness/Brightness.pde rename to processing/mode/examples/Basics/Color/Brightness/Brightness.pde diff --git a/examples/Basics/Color/ColorWheel/ColorWheel.pde b/processing/mode/examples/Basics/Color/ColorWheel/ColorWheel.pde similarity index 100% rename from examples/Basics/Color/ColorWheel/ColorWheel.pde rename to processing/mode/examples/Basics/Color/ColorWheel/ColorWheel.pde diff --git a/examples/Basics/Color/Creating/Creating.pde b/processing/mode/examples/Basics/Color/Creating/Creating.pde similarity index 100% rename from examples/Basics/Color/Creating/Creating.pde rename to processing/mode/examples/Basics/Color/Creating/Creating.pde diff --git a/examples/Basics/Color/Hue/Hue.pde b/processing/mode/examples/Basics/Color/Hue/Hue.pde similarity index 100% rename from examples/Basics/Color/Hue/Hue.pde rename to processing/mode/examples/Basics/Color/Hue/Hue.pde diff --git a/examples/Basics/Color/LinearGradient/LinearGradient.pde b/processing/mode/examples/Basics/Color/LinearGradient/LinearGradient.pde similarity index 100% rename from examples/Basics/Color/LinearGradient/LinearGradient.pde rename to processing/mode/examples/Basics/Color/LinearGradient/LinearGradient.pde diff --git a/examples/Basics/Color/RadialGradient/RadialGradient.pde b/processing/mode/examples/Basics/Color/RadialGradient/RadialGradient.pde similarity index 100% rename from examples/Basics/Color/RadialGradient/RadialGradient.pde rename to processing/mode/examples/Basics/Color/RadialGradient/RadialGradient.pde diff --git a/examples/Basics/Color/RadialGradient2/RadialGradient2.pde b/processing/mode/examples/Basics/Color/RadialGradient2/RadialGradient2.pde similarity index 100% rename from examples/Basics/Color/RadialGradient2/RadialGradient2.pde rename to processing/mode/examples/Basics/Color/RadialGradient2/RadialGradient2.pde diff --git a/examples/Basics/Color/Reading/Reading.pde b/processing/mode/examples/Basics/Color/Reading/Reading.pde similarity index 100% rename from examples/Basics/Color/Reading/Reading.pde rename to processing/mode/examples/Basics/Color/Reading/Reading.pde diff --git a/examples/Basics/Color/Reading/data/cait.jpg b/processing/mode/examples/Basics/Color/Reading/data/cait.jpg similarity index 100% rename from examples/Basics/Color/Reading/data/cait.jpg rename to processing/mode/examples/Basics/Color/Reading/data/cait.jpg diff --git a/examples/Basics/Color/Relativity/Relativity.pde b/processing/mode/examples/Basics/Color/Relativity/Relativity.pde similarity index 100% rename from examples/Basics/Color/Relativity/Relativity.pde rename to processing/mode/examples/Basics/Color/Relativity/Relativity.pde diff --git a/examples/Basics/Color/Saturation/Saturation.pde b/processing/mode/examples/Basics/Color/Saturation/Saturation.pde similarity index 100% rename from examples/Basics/Color/Saturation/Saturation.pde rename to processing/mode/examples/Basics/Color/Saturation/Saturation.pde diff --git a/examples/Basics/Color/WaveGradient/WaveGradient.pde b/processing/mode/examples/Basics/Color/WaveGradient/WaveGradient.pde similarity index 100% rename from examples/Basics/Color/WaveGradient/WaveGradient.pde rename to processing/mode/examples/Basics/Color/WaveGradient/WaveGradient.pde diff --git a/examples/Basics/Control/Conditionals1/Conditionals1.pde b/processing/mode/examples/Basics/Control/Conditionals1/Conditionals1.pde similarity index 100% rename from examples/Basics/Control/Conditionals1/Conditionals1.pde rename to processing/mode/examples/Basics/Control/Conditionals1/Conditionals1.pde diff --git a/examples/Basics/Control/Conditionals2/Conditionals2.pde b/processing/mode/examples/Basics/Control/Conditionals2/Conditionals2.pde similarity index 100% rename from examples/Basics/Control/Conditionals2/Conditionals2.pde rename to processing/mode/examples/Basics/Control/Conditionals2/Conditionals2.pde diff --git a/examples/Basics/Control/EmbeddedIteration/EmbeddedIteration.pde b/processing/mode/examples/Basics/Control/EmbeddedIteration/EmbeddedIteration.pde similarity index 100% rename from examples/Basics/Control/EmbeddedIteration/EmbeddedIteration.pde rename to processing/mode/examples/Basics/Control/EmbeddedIteration/EmbeddedIteration.pde diff --git a/examples/Basics/Control/Iteration/Iteration.pde b/processing/mode/examples/Basics/Control/Iteration/Iteration.pde similarity index 100% rename from examples/Basics/Control/Iteration/Iteration.pde rename to processing/mode/examples/Basics/Control/Iteration/Iteration.pde diff --git a/examples/Basics/Control/LogicalOperators/LogicalOperators.pde b/processing/mode/examples/Basics/Control/LogicalOperators/LogicalOperators.pde similarity index 100% rename from examples/Basics/Control/LogicalOperators/LogicalOperators.pde rename to processing/mode/examples/Basics/Control/LogicalOperators/LogicalOperators.pde diff --git a/examples/Basics/Data/CharactersStrings/CharactersStrings.pde b/processing/mode/examples/Basics/Data/CharactersStrings/CharactersStrings.pde similarity index 100% rename from examples/Basics/Data/CharactersStrings/CharactersStrings.pde rename to processing/mode/examples/Basics/Data/CharactersStrings/CharactersStrings.pde diff --git a/examples/Basics/Data/CharactersStrings/data/Eureka-90.vlw b/processing/mode/examples/Basics/Data/CharactersStrings/data/Eureka-90.vlw similarity index 100% rename from examples/Basics/Data/CharactersStrings/data/Eureka-90.vlw rename to processing/mode/examples/Basics/Data/CharactersStrings/data/Eureka-90.vlw diff --git a/examples/Basics/Data/CharactersStrings/data/rathausFrog.jpg b/processing/mode/examples/Basics/Data/CharactersStrings/data/rathausFrog.jpg similarity index 100% rename from examples/Basics/Data/CharactersStrings/data/rathausFrog.jpg rename to processing/mode/examples/Basics/Data/CharactersStrings/data/rathausFrog.jpg diff --git a/examples/Basics/Data/DatatypeConversion/DatatypeConversion.pde b/processing/mode/examples/Basics/Data/DatatypeConversion/DatatypeConversion.pde similarity index 100% rename from examples/Basics/Data/DatatypeConversion/DatatypeConversion.pde rename to processing/mode/examples/Basics/Data/DatatypeConversion/DatatypeConversion.pde diff --git a/examples/Basics/Data/IntegersFloats/IntegersFloats.pde b/processing/mode/examples/Basics/Data/IntegersFloats/IntegersFloats.pde similarity index 100% rename from examples/Basics/Data/IntegersFloats/IntegersFloats.pde rename to processing/mode/examples/Basics/Data/IntegersFloats/IntegersFloats.pde diff --git a/examples/Basics/Data/TrueFalse/TrueFalse.pde b/processing/mode/examples/Basics/Data/TrueFalse/TrueFalse.pde similarity index 100% rename from examples/Basics/Data/TrueFalse/TrueFalse.pde rename to processing/mode/examples/Basics/Data/TrueFalse/TrueFalse.pde diff --git a/examples/Basics/Data/VariableScope/VariableScope.pde b/processing/mode/examples/Basics/Data/VariableScope/VariableScope.pde similarity index 100% rename from examples/Basics/Data/VariableScope/VariableScope.pde rename to processing/mode/examples/Basics/Data/VariableScope/VariableScope.pde diff --git a/examples/Basics/Data/Variables/Variables.pde b/processing/mode/examples/Basics/Data/Variables/Variables.pde similarity index 100% rename from examples/Basics/Data/Variables/Variables.pde rename to processing/mode/examples/Basics/Data/Variables/Variables.pde diff --git a/examples/Basics/Form/Bezier/Bezier.pde b/processing/mode/examples/Basics/Form/Bezier/Bezier.pde similarity index 100% rename from examples/Basics/Form/Bezier/Bezier.pde rename to processing/mode/examples/Basics/Form/Bezier/Bezier.pde diff --git a/examples/Basics/Form/BezierEllipse/BezierEllipse.pde b/processing/mode/examples/Basics/Form/BezierEllipse/BezierEllipse.pde similarity index 100% rename from examples/Basics/Form/BezierEllipse/BezierEllipse.pde rename to processing/mode/examples/Basics/Form/BezierEllipse/BezierEllipse.pde diff --git a/examples/Basics/Form/PieChart/PieChart.pde b/processing/mode/examples/Basics/Form/PieChart/PieChart.pde similarity index 100% rename from examples/Basics/Form/PieChart/PieChart.pde rename to processing/mode/examples/Basics/Form/PieChart/PieChart.pde diff --git a/examples/Basics/Form/PointsLines/PointsLines.pde b/processing/mode/examples/Basics/Form/PointsLines/PointsLines.pde similarity index 100% rename from examples/Basics/Form/PointsLines/PointsLines.pde rename to processing/mode/examples/Basics/Form/PointsLines/PointsLines.pde diff --git a/examples/Basics/Form/Primitives3D/Primitives3D.pde b/processing/mode/examples/Basics/Form/Primitives3D/Primitives3D.pde similarity index 100% rename from examples/Basics/Form/Primitives3D/Primitives3D.pde rename to processing/mode/examples/Basics/Form/Primitives3D/Primitives3D.pde diff --git a/examples/Basics/Form/ShapePrimitives/ShapePrimitives.pde b/processing/mode/examples/Basics/Form/ShapePrimitives/ShapePrimitives.pde similarity index 100% rename from examples/Basics/Form/ShapePrimitives/ShapePrimitives.pde rename to processing/mode/examples/Basics/Form/ShapePrimitives/ShapePrimitives.pde diff --git a/examples/Basics/Form/SimpleCurves/SimpleCurves.pde b/processing/mode/examples/Basics/Form/SimpleCurves/SimpleCurves.pde similarity index 100% rename from examples/Basics/Form/SimpleCurves/SimpleCurves.pde rename to processing/mode/examples/Basics/Form/SimpleCurves/SimpleCurves.pde diff --git a/examples/Basics/Form/TriangleStrip/TriangleStrip.pde b/processing/mode/examples/Basics/Form/TriangleStrip/TriangleStrip.pde similarity index 100% rename from examples/Basics/Form/TriangleStrip/TriangleStrip.pde rename to processing/mode/examples/Basics/Form/TriangleStrip/TriangleStrip.pde diff --git a/examples/Basics/Form/Vertices/Vertices.pde b/processing/mode/examples/Basics/Form/Vertices/Vertices.pde similarity index 100% rename from examples/Basics/Form/Vertices/Vertices.pde rename to processing/mode/examples/Basics/Form/Vertices/Vertices.pde diff --git a/examples/Basics/Image/Alphamask/Alphamask.pde b/processing/mode/examples/Basics/Image/Alphamask/Alphamask.pde similarity index 100% rename from examples/Basics/Image/Alphamask/Alphamask.pde rename to processing/mode/examples/Basics/Image/Alphamask/Alphamask.pde diff --git a/examples/Basics/Image/Alphamask/data/mask.jpg b/processing/mode/examples/Basics/Image/Alphamask/data/mask.jpg similarity index 100% rename from examples/Basics/Image/Alphamask/data/mask.jpg rename to processing/mode/examples/Basics/Image/Alphamask/data/mask.jpg diff --git a/examples/Basics/Image/Alphamask/data/test.jpg b/processing/mode/examples/Basics/Image/Alphamask/data/test.jpg similarity index 100% rename from examples/Basics/Image/Alphamask/data/test.jpg rename to processing/mode/examples/Basics/Image/Alphamask/data/test.jpg diff --git a/examples/Basics/Image/BackgroundImage/BackgroundImage.pde b/processing/mode/examples/Basics/Image/BackgroundImage/BackgroundImage.pde similarity index 100% rename from examples/Basics/Image/BackgroundImage/BackgroundImage.pde rename to processing/mode/examples/Basics/Image/BackgroundImage/BackgroundImage.pde diff --git a/examples/Basics/Image/BackgroundImage/data/milan_rubbish.jpg b/processing/mode/examples/Basics/Image/BackgroundImage/data/milan_rubbish.jpg similarity index 100% rename from examples/Basics/Image/BackgroundImage/data/milan_rubbish.jpg rename to processing/mode/examples/Basics/Image/BackgroundImage/data/milan_rubbish.jpg diff --git a/examples/Basics/Image/CreateImage/CreateImage.pde b/processing/mode/examples/Basics/Image/CreateImage/CreateImage.pde similarity index 95% rename from examples/Basics/Image/CreateImage/CreateImage.pde rename to processing/mode/examples/Basics/Image/CreateImage/CreateImage.pde index df095a75e..04e42d07b 100644 --- a/examples/Basics/Image/CreateImage/CreateImage.pde +++ b/processing/mode/examples/Basics/Image/CreateImage/CreateImage.pde @@ -11,6 +11,7 @@ void setup() { size(200, 200); img = createImage(120, 120, ARGB); + img.loadPixels(); for(int i=0; i < img.pixels.length; i++) { img.pixels[i] = color(0, 90, 102, i%img.width * 2); } diff --git a/examples/Basics/Image/CreateImage/data/mask.jpg b/processing/mode/examples/Basics/Image/CreateImage/data/mask.jpg similarity index 100% rename from examples/Basics/Image/CreateImage/data/mask.jpg rename to processing/mode/examples/Basics/Image/CreateImage/data/mask.jpg diff --git a/examples/Basics/Image/CreateImage/data/test.jpg b/processing/mode/examples/Basics/Image/CreateImage/data/test.jpg similarity index 100% rename from examples/Basics/Image/CreateImage/data/test.jpg rename to processing/mode/examples/Basics/Image/CreateImage/data/test.jpg diff --git a/examples/Basics/Image/LoadDisplayImage/LoadDisplayImage.pde b/processing/mode/examples/Basics/Image/LoadDisplayImage/LoadDisplayImage.pde similarity index 93% rename from examples/Basics/Image/LoadDisplayImage/LoadDisplayImage.pde rename to processing/mode/examples/Basics/Image/LoadDisplayImage/LoadDisplayImage.pde index 8252c5cbc..495d1f124 100644 --- a/examples/Basics/Image/LoadDisplayImage/LoadDisplayImage.pde +++ b/processing/mode/examples/Basics/Image/LoadDisplayImage/LoadDisplayImage.pde @@ -12,7 +12,6 @@ void setup() { // The file "jelly.jpg" must be in the data folder // of the current sketch to load successfully a = loadImage("jelly.jpg"); // Load the image into the program - noLoop(); // Makes draw() only run once } void draw() { diff --git a/examples/Basics/Image/LoadDisplayImage/data/jelly.jpg b/processing/mode/examples/Basics/Image/LoadDisplayImage/data/jelly.jpg similarity index 100% rename from examples/Basics/Image/LoadDisplayImage/data/jelly.jpg rename to processing/mode/examples/Basics/Image/LoadDisplayImage/data/jelly.jpg diff --git a/examples/Basics/Image/Pointillism/Pointillism.pde b/processing/mode/examples/Basics/Image/Pointillism/Pointillism.pde similarity index 100% rename from examples/Basics/Image/Pointillism/Pointillism.pde rename to processing/mode/examples/Basics/Image/Pointillism/Pointillism.pde diff --git a/examples/Basics/Image/Pointillism/data/eames.jpg b/processing/mode/examples/Basics/Image/Pointillism/data/eames.jpg similarity index 100% rename from examples/Basics/Image/Pointillism/data/eames.jpg rename to processing/mode/examples/Basics/Image/Pointillism/data/eames.jpg diff --git a/examples/Basics/Image/Pointillism/data/sunflower.jpg b/processing/mode/examples/Basics/Image/Pointillism/data/sunflower.jpg similarity index 100% rename from examples/Basics/Image/Pointillism/data/sunflower.jpg rename to processing/mode/examples/Basics/Image/Pointillism/data/sunflower.jpg diff --git a/examples/Basics/Image/RequestImage/RequestImage.pde b/processing/mode/examples/Basics/Image/RequestImage/RequestImage.pde similarity index 100% rename from examples/Basics/Image/RequestImage/RequestImage.pde rename to processing/mode/examples/Basics/Image/RequestImage/RequestImage.pde diff --git a/examples/Basics/Image/Sprite/Sprite.pde b/processing/mode/examples/Basics/Image/Sprite/Sprite.pde similarity index 100% rename from examples/Basics/Image/Sprite/Sprite.pde rename to processing/mode/examples/Basics/Image/Sprite/Sprite.pde diff --git a/examples/Basics/Image/Sprite/data/teddy.gif b/processing/mode/examples/Basics/Image/Sprite/data/teddy.gif similarity index 100% rename from examples/Basics/Image/Sprite/data/teddy.gif rename to processing/mode/examples/Basics/Image/Sprite/data/teddy.gif diff --git a/examples/Basics/Image/Sprite2/Sprite2.pde b/processing/mode/examples/Basics/Image/Sprite2/Sprite2.pde similarity index 100% rename from examples/Basics/Image/Sprite2/Sprite2.pde rename to processing/mode/examples/Basics/Image/Sprite2/Sprite2.pde diff --git a/examples/Basics/Image/Sprite2/data/sky.jpg b/processing/mode/examples/Basics/Image/Sprite2/data/sky.jpg similarity index 100% rename from examples/Basics/Image/Sprite2/data/sky.jpg rename to processing/mode/examples/Basics/Image/Sprite2/data/sky.jpg diff --git a/examples/Basics/Image/Sprite2/data/teddy.gif b/processing/mode/examples/Basics/Image/Sprite2/data/teddy.gif similarity index 100% rename from examples/Basics/Image/Sprite2/data/teddy.gif rename to processing/mode/examples/Basics/Image/Sprite2/data/teddy.gif diff --git a/examples/Basics/Image/Transparency/Transparency.pde b/processing/mode/examples/Basics/Image/Transparency/Transparency.pde similarity index 100% rename from examples/Basics/Image/Transparency/Transparency.pde rename to processing/mode/examples/Basics/Image/Transparency/Transparency.pde diff --git a/examples/Basics/Image/Transparency/data/construct.jpg b/processing/mode/examples/Basics/Image/Transparency/data/construct.jpg similarity index 100% rename from examples/Basics/Image/Transparency/data/construct.jpg rename to processing/mode/examples/Basics/Image/Transparency/data/construct.jpg diff --git a/examples/Basics/Image/Transparency/data/wash.jpg b/processing/mode/examples/Basics/Image/Transparency/data/wash.jpg similarity index 100% rename from examples/Basics/Image/Transparency/data/wash.jpg rename to processing/mode/examples/Basics/Image/Transparency/data/wash.jpg diff --git a/examples/Basics/Input/Clock/Clock.pde b/processing/mode/examples/Basics/Input/Clock/Clock.pde similarity index 100% rename from examples/Basics/Input/Clock/Clock.pde rename to processing/mode/examples/Basics/Input/Clock/Clock.pde diff --git a/examples/Basics/Input/Constrain/Constrain.pde b/processing/mode/examples/Basics/Input/Constrain/Constrain.pde similarity index 100% rename from examples/Basics/Input/Constrain/Constrain.pde rename to processing/mode/examples/Basics/Input/Constrain/Constrain.pde diff --git a/examples/Basics/Input/Easing/Easing.pde b/processing/mode/examples/Basics/Input/Easing/Easing.pde similarity index 100% rename from examples/Basics/Input/Easing/Easing.pde rename to processing/mode/examples/Basics/Input/Easing/Easing.pde diff --git a/examples/Basics/Input/Keyboard/Keyboard.pde b/processing/mode/examples/Basics/Input/Keyboard/Keyboard.pde similarity index 100% rename from examples/Basics/Input/Keyboard/Keyboard.pde rename to processing/mode/examples/Basics/Input/Keyboard/Keyboard.pde diff --git a/examples/Basics/Input/KeyboardFunctions/KeyboardFunctions.pde b/processing/mode/examples/Basics/Input/KeyboardFunctions/KeyboardFunctions.pde similarity index 100% rename from examples/Basics/Input/KeyboardFunctions/KeyboardFunctions.pde rename to processing/mode/examples/Basics/Input/KeyboardFunctions/KeyboardFunctions.pde diff --git a/examples/Basics/Input/KeyboardFunctions/data/brugges.jpg b/processing/mode/examples/Basics/Input/KeyboardFunctions/data/brugges.jpg similarity index 100% rename from examples/Basics/Input/KeyboardFunctions/data/brugges.jpg rename to processing/mode/examples/Basics/Input/KeyboardFunctions/data/brugges.jpg diff --git a/examples/Basics/Input/Milliseconds/Milliseconds.pde b/processing/mode/examples/Basics/Input/Milliseconds/Milliseconds.pde similarity index 100% rename from examples/Basics/Input/Milliseconds/Milliseconds.pde rename to processing/mode/examples/Basics/Input/Milliseconds/Milliseconds.pde diff --git a/examples/Basics/Input/Mouse1D/Mouse1D.pde b/processing/mode/examples/Basics/Input/Mouse1D/Mouse1D.pde similarity index 100% rename from examples/Basics/Input/Mouse1D/Mouse1D.pde rename to processing/mode/examples/Basics/Input/Mouse1D/Mouse1D.pde diff --git a/examples/Basics/Input/Mouse2D/Mouse2D.pde b/processing/mode/examples/Basics/Input/Mouse2D/Mouse2D.pde similarity index 100% rename from examples/Basics/Input/Mouse2D/Mouse2D.pde rename to processing/mode/examples/Basics/Input/Mouse2D/Mouse2D.pde diff --git a/examples/Basics/Input/MouseFunctions/MouseFunctions.pde b/processing/mode/examples/Basics/Input/MouseFunctions/MouseFunctions.pde similarity index 100% rename from examples/Basics/Input/MouseFunctions/MouseFunctions.pde rename to processing/mode/examples/Basics/Input/MouseFunctions/MouseFunctions.pde diff --git a/examples/Basics/Input/MousePress/MousePress.pde b/processing/mode/examples/Basics/Input/MousePress/MousePress.pde similarity index 100% rename from examples/Basics/Input/MousePress/MousePress.pde rename to processing/mode/examples/Basics/Input/MousePress/MousePress.pde diff --git a/examples/Basics/Input/MouseSignals/MouseSignals.pde b/processing/mode/examples/Basics/Input/MouseSignals/MouseSignals.pde similarity index 100% rename from examples/Basics/Input/MouseSignals/MouseSignals.pde rename to processing/mode/examples/Basics/Input/MouseSignals/MouseSignals.pde diff --git a/examples/Basics/Input/StoringInput/StoringInput.pde b/processing/mode/examples/Basics/Input/StoringInput/StoringInput.pde similarity index 100% rename from examples/Basics/Input/StoringInput/StoringInput.pde rename to processing/mode/examples/Basics/Input/StoringInput/StoringInput.pde diff --git a/examples/Basics/Lights/Directional/Directional.pde b/processing/mode/examples/Basics/Lights/Directional/Directional.pde similarity index 100% rename from examples/Basics/Lights/Directional/Directional.pde rename to processing/mode/examples/Basics/Lights/Directional/Directional.pde diff --git a/examples/Basics/Lights/Mixture/Mixture.pde b/processing/mode/examples/Basics/Lights/Mixture/Mixture.pde similarity index 100% rename from examples/Basics/Lights/Mixture/Mixture.pde rename to processing/mode/examples/Basics/Lights/Mixture/Mixture.pde diff --git a/examples/Basics/Lights/MixtureGrid/MixtureGrid.pde b/processing/mode/examples/Basics/Lights/MixtureGrid/MixtureGrid.pde similarity index 100% rename from examples/Basics/Lights/MixtureGrid/MixtureGrid.pde rename to processing/mode/examples/Basics/Lights/MixtureGrid/MixtureGrid.pde diff --git a/examples/Basics/Lights/OnOff/OnOff.pde b/processing/mode/examples/Basics/Lights/OnOff/OnOff.pde similarity index 100% rename from examples/Basics/Lights/OnOff/OnOff.pde rename to processing/mode/examples/Basics/Lights/OnOff/OnOff.pde diff --git a/examples/Basics/Lights/Reflection/Reflection.pde b/processing/mode/examples/Basics/Lights/Reflection/Reflection.pde similarity index 100% rename from examples/Basics/Lights/Reflection/Reflection.pde rename to processing/mode/examples/Basics/Lights/Reflection/Reflection.pde diff --git a/examples/Basics/Lights/Spot/Spot.pde b/processing/mode/examples/Basics/Lights/Spot/Spot.pde similarity index 100% rename from examples/Basics/Lights/Spot/Spot.pde rename to processing/mode/examples/Basics/Lights/Spot/Spot.pde diff --git a/examples/Basics/Math/AdditiveWave/AdditiveWave.pde b/processing/mode/examples/Basics/Math/AdditiveWave/AdditiveWave.pde similarity index 100% rename from examples/Basics/Math/AdditiveWave/AdditiveWave.pde rename to processing/mode/examples/Basics/Math/AdditiveWave/AdditiveWave.pde diff --git a/examples/Basics/Math/Arctangent/Arctangent.pde b/processing/mode/examples/Basics/Math/Arctangent/Arctangent.pde similarity index 100% rename from examples/Basics/Math/Arctangent/Arctangent.pde rename to processing/mode/examples/Basics/Math/Arctangent/Arctangent.pde diff --git a/examples/Basics/Math/Distance1D/Distance1D.pde b/processing/mode/examples/Basics/Math/Distance1D/Distance1D.pde similarity index 100% rename from examples/Basics/Math/Distance1D/Distance1D.pde rename to processing/mode/examples/Basics/Math/Distance1D/Distance1D.pde diff --git a/examples/Basics/Math/Distance2D/Distance2D.pde b/processing/mode/examples/Basics/Math/Distance2D/Distance2D.pde similarity index 100% rename from examples/Basics/Math/Distance2D/Distance2D.pde rename to processing/mode/examples/Basics/Math/Distance2D/Distance2D.pde diff --git a/examples/Basics/Math/DoubleRandom/DoubleRandom.pde b/processing/mode/examples/Basics/Math/DoubleRandom/DoubleRandom.pde similarity index 100% rename from examples/Basics/Math/DoubleRandom/DoubleRandom.pde rename to processing/mode/examples/Basics/Math/DoubleRandom/DoubleRandom.pde diff --git a/examples/Basics/Math/Graphing2DEquation/Graphing2DEquation.pde b/processing/mode/examples/Basics/Math/Graphing2DEquation/Graphing2DEquation.pde similarity index 100% rename from examples/Basics/Math/Graphing2DEquation/Graphing2DEquation.pde rename to processing/mode/examples/Basics/Math/Graphing2DEquation/Graphing2DEquation.pde diff --git a/examples/Basics/Math/IncrementDecrement/IncrementDecrement.pde b/processing/mode/examples/Basics/Math/IncrementDecrement/IncrementDecrement.pde similarity index 100% rename from examples/Basics/Math/IncrementDecrement/IncrementDecrement.pde rename to processing/mode/examples/Basics/Math/IncrementDecrement/IncrementDecrement.pde diff --git a/examples/Basics/Math/Modulo/Modulo.pde b/processing/mode/examples/Basics/Math/Modulo/Modulo.pde similarity index 100% rename from examples/Basics/Math/Modulo/Modulo.pde rename to processing/mode/examples/Basics/Math/Modulo/Modulo.pde diff --git a/examples/Basics/Math/Noise1D/Noise1D.pde b/processing/mode/examples/Basics/Math/Noise1D/Noise1D.pde similarity index 100% rename from examples/Basics/Math/Noise1D/Noise1D.pde rename to processing/mode/examples/Basics/Math/Noise1D/Noise1D.pde diff --git a/examples/Basics/Math/Noise2D/Noise2D.pde b/processing/mode/examples/Basics/Math/Noise2D/Noise2D.pde similarity index 100% rename from examples/Basics/Math/Noise2D/Noise2D.pde rename to processing/mode/examples/Basics/Math/Noise2D/Noise2D.pde diff --git a/examples/Basics/Math/Noise3D/Noise3D.pde b/processing/mode/examples/Basics/Math/Noise3D/Noise3D.pde similarity index 100% rename from examples/Basics/Math/Noise3D/Noise3D.pde rename to processing/mode/examples/Basics/Math/Noise3D/Noise3D.pde diff --git a/examples/Basics/Math/NoiseWave/NoiseWave.pde b/processing/mode/examples/Basics/Math/NoiseWave/NoiseWave.pde similarity index 100% rename from examples/Basics/Math/NoiseWave/NoiseWave.pde rename to processing/mode/examples/Basics/Math/NoiseWave/NoiseWave.pde diff --git a/examples/Basics/Math/OperatorPrecedence/OperatorPrecedence.pde b/processing/mode/examples/Basics/Math/OperatorPrecedence/OperatorPrecedence.pde similarity index 100% rename from examples/Basics/Math/OperatorPrecedence/OperatorPrecedence.pde rename to processing/mode/examples/Basics/Math/OperatorPrecedence/OperatorPrecedence.pde diff --git a/examples/Basics/Math/PolarToCartesian/PolarToCartesian.pde b/processing/mode/examples/Basics/Math/PolarToCartesian/PolarToCartesian.pde similarity index 100% rename from examples/Basics/Math/PolarToCartesian/PolarToCartesian.pde rename to processing/mode/examples/Basics/Math/PolarToCartesian/PolarToCartesian.pde diff --git a/examples/Basics/Math/Random/Random.pde b/processing/mode/examples/Basics/Math/Random/Random.pde similarity index 100% rename from examples/Basics/Math/Random/Random.pde rename to processing/mode/examples/Basics/Math/Random/Random.pde diff --git a/examples/Basics/Math/Sine/Sine.pde b/processing/mode/examples/Basics/Math/Sine/Sine.pde similarity index 100% rename from examples/Basics/Math/Sine/Sine.pde rename to processing/mode/examples/Basics/Math/Sine/Sine.pde diff --git a/examples/Basics/Math/SineCosine/SineCosine.pde b/processing/mode/examples/Basics/Math/SineCosine/SineCosine.pde similarity index 100% rename from examples/Basics/Math/SineCosine/SineCosine.pde rename to processing/mode/examples/Basics/Math/SineCosine/SineCosine.pde diff --git a/examples/Basics/Math/SineWave/SineWave.pde b/processing/mode/examples/Basics/Math/SineWave/SineWave.pde similarity index 100% rename from examples/Basics/Math/SineWave/SineWave.pde rename to processing/mode/examples/Basics/Math/SineWave/SineWave.pde diff --git a/examples/Basics/Objects/CompositeObjects/CompositeObjects.pde b/processing/mode/examples/Basics/Objects/CompositeObjects/CompositeObjects.pde similarity index 100% rename from examples/Basics/Objects/CompositeObjects/CompositeObjects.pde rename to processing/mode/examples/Basics/Objects/CompositeObjects/CompositeObjects.pde diff --git a/examples/Basics/Objects/CompositeObjects/Egg.pde b/processing/mode/examples/Basics/Objects/CompositeObjects/Egg.pde similarity index 100% rename from examples/Basics/Objects/CompositeObjects/Egg.pde rename to processing/mode/examples/Basics/Objects/CompositeObjects/Egg.pde diff --git a/examples/Basics/Objects/CompositeObjects/EggRing.pde b/processing/mode/examples/Basics/Objects/CompositeObjects/EggRing.pde similarity index 100% rename from examples/Basics/Objects/CompositeObjects/EggRing.pde rename to processing/mode/examples/Basics/Objects/CompositeObjects/EggRing.pde diff --git a/examples/Basics/Objects/CompositeObjects/Ring.pde b/processing/mode/examples/Basics/Objects/CompositeObjects/Ring.pde similarity index 100% rename from examples/Basics/Objects/CompositeObjects/Ring.pde rename to processing/mode/examples/Basics/Objects/CompositeObjects/Ring.pde diff --git a/examples/Basics/Objects/Inheritance/Inheritance.pde b/processing/mode/examples/Basics/Objects/Inheritance/Inheritance.pde similarity index 100% rename from examples/Basics/Objects/Inheritance/Inheritance.pde rename to processing/mode/examples/Basics/Objects/Inheritance/Inheritance.pde diff --git a/examples/Basics/Objects/MultipleConstructors/MultipleConstructors.pde b/processing/mode/examples/Basics/Objects/MultipleConstructors/MultipleConstructors.pde similarity index 100% rename from examples/Basics/Objects/MultipleConstructors/MultipleConstructors.pde rename to processing/mode/examples/Basics/Objects/MultipleConstructors/MultipleConstructors.pde diff --git a/examples/Basics/Objects/Neighborhood/Neighborhood.pde b/processing/mode/examples/Basics/Objects/Neighborhood/Neighborhood.pde similarity index 100% rename from examples/Basics/Objects/Neighborhood/Neighborhood.pde rename to processing/mode/examples/Basics/Objects/Neighborhood/Neighborhood.pde diff --git a/examples/Basics/Objects/Objects/Objects.pde b/processing/mode/examples/Basics/Objects/Objects/Objects.pde similarity index 100% rename from examples/Basics/Objects/Objects/Objects.pde rename to processing/mode/examples/Basics/Objects/Objects/Objects.pde diff --git a/examples/Basics/Shape/DisableStyle/DisableStyle.pde b/processing/mode/examples/Basics/Shape/DisableStyle/DisableStyle.pde similarity index 100% rename from examples/Basics/Shape/DisableStyle/DisableStyle.pde rename to processing/mode/examples/Basics/Shape/DisableStyle/DisableStyle.pde diff --git a/examples/Basics/Shape/LoadDisplaySVG/data/bot1.svg b/processing/mode/examples/Basics/Shape/DisableStyle/data/bot1.svg similarity index 100% rename from examples/Basics/Shape/LoadDisplaySVG/data/bot1.svg rename to processing/mode/examples/Basics/Shape/DisableStyle/data/bot1.svg diff --git a/examples/Basics/Shape/GetChild/GetChild.pde b/processing/mode/examples/Basics/Shape/GetChild/GetChild.pde similarity index 100% rename from examples/Basics/Shape/GetChild/GetChild.pde rename to processing/mode/examples/Basics/Shape/GetChild/GetChild.pde diff --git a/examples/Basics/Shape/GetChild/data/usa-wikipedia.svg b/processing/mode/examples/Basics/Shape/GetChild/data/usa-wikipedia.svg similarity index 100% rename from examples/Basics/Shape/GetChild/data/usa-wikipedia.svg rename to processing/mode/examples/Basics/Shape/GetChild/data/usa-wikipedia.svg diff --git a/examples/Basics/Shape/LoadDisplayOBJ/LoadDisplayOBJ.pde b/processing/mode/examples/Basics/Shape/LoadDisplayOBJ/LoadDisplayOBJ.pde similarity index 100% rename from examples/Basics/Shape/LoadDisplayOBJ/LoadDisplayOBJ.pde rename to processing/mode/examples/Basics/Shape/LoadDisplayOBJ/LoadDisplayOBJ.pde diff --git a/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.mtl b/processing/mode/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.mtl similarity index 100% rename from examples/Basics/Shape/LoadDisplayOBJ/data/rocket.mtl rename to processing/mode/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.mtl diff --git a/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.obj b/processing/mode/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.obj similarity index 100% rename from examples/Basics/Shape/LoadDisplayOBJ/data/rocket.obj rename to processing/mode/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.obj diff --git a/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.png b/processing/mode/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.png similarity index 100% rename from examples/Basics/Shape/LoadDisplayOBJ/data/rocket.png rename to processing/mode/examples/Basics/Shape/LoadDisplayOBJ/data/rocket.png diff --git a/examples/Basics/Shape/LoadDisplaySVG/LoadDisplaySVG.pde b/processing/mode/examples/Basics/Shape/LoadDisplaySVG/LoadDisplaySVG.pde similarity index 100% rename from examples/Basics/Shape/LoadDisplaySVG/LoadDisplaySVG.pde rename to processing/mode/examples/Basics/Shape/LoadDisplaySVG/LoadDisplaySVG.pde diff --git a/examples/Basics/Shape/ScaleShape/data/bot1.svg b/processing/mode/examples/Basics/Shape/LoadDisplaySVG/data/bot1.svg similarity index 100% rename from examples/Basics/Shape/ScaleShape/data/bot1.svg rename to processing/mode/examples/Basics/Shape/LoadDisplaySVG/data/bot1.svg diff --git a/examples/Basics/Shape/ScaleShape/ScaleShape.pde b/processing/mode/examples/Basics/Shape/ScaleShape/ScaleShape.pde similarity index 100% rename from examples/Basics/Shape/ScaleShape/ScaleShape.pde rename to processing/mode/examples/Basics/Shape/ScaleShape/ScaleShape.pde diff --git a/processing/mode/examples/Basics/Shape/ScaleShape/data/bot1.svg b/processing/mode/examples/Basics/Shape/ScaleShape/data/bot1.svg new file mode 100644 index 000000000..3c56f2d60 --- /dev/null +++ b/processing/mode/examples/Basics/Shape/ScaleShape/data/bot1.svg @@ -0,0 +1,160 @@ + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/Basics/Structure/Coordinates/Coordinates.pde b/processing/mode/examples/Basics/Structure/Coordinates/Coordinates.pde similarity index 100% rename from examples/Basics/Structure/Coordinates/Coordinates.pde rename to processing/mode/examples/Basics/Structure/Coordinates/Coordinates.pde diff --git a/examples/Basics/Structure/CreateGraphics/CreateGraphics.pde b/processing/mode/examples/Basics/Structure/CreateGraphics/CreateGraphics.pde similarity index 100% rename from examples/Basics/Structure/CreateGraphics/CreateGraphics.pde rename to processing/mode/examples/Basics/Structure/CreateGraphics/CreateGraphics.pde diff --git a/examples/Basics/Structure/CreateGraphics/data/mask.jpg b/processing/mode/examples/Basics/Structure/CreateGraphics/data/mask.jpg similarity index 100% rename from examples/Basics/Structure/CreateGraphics/data/mask.jpg rename to processing/mode/examples/Basics/Structure/CreateGraphics/data/mask.jpg diff --git a/examples/Basics/Structure/CreateGraphics/data/test.jpg b/processing/mode/examples/Basics/Structure/CreateGraphics/data/test.jpg similarity index 100% rename from examples/Basics/Structure/CreateGraphics/data/test.jpg rename to processing/mode/examples/Basics/Structure/CreateGraphics/data/test.jpg diff --git a/examples/Basics/Structure/Functions/Functions.pde b/processing/mode/examples/Basics/Structure/Functions/Functions.pde similarity index 100% rename from examples/Basics/Structure/Functions/Functions.pde rename to processing/mode/examples/Basics/Structure/Functions/Functions.pde diff --git a/examples/Basics/Structure/Loop/Loop.pde b/processing/mode/examples/Basics/Structure/Loop/Loop.pde similarity index 100% rename from examples/Basics/Structure/Loop/Loop.pde rename to processing/mode/examples/Basics/Structure/Loop/Loop.pde diff --git a/examples/Basics/Structure/NoLoop/NoLoop.pde b/processing/mode/examples/Basics/Structure/NoLoop/NoLoop.pde similarity index 100% rename from examples/Basics/Structure/NoLoop/NoLoop.pde rename to processing/mode/examples/Basics/Structure/NoLoop/NoLoop.pde diff --git a/examples/Basics/Structure/Recursion/Recursion.pde b/processing/mode/examples/Basics/Structure/Recursion/Recursion.pde similarity index 100% rename from examples/Basics/Structure/Recursion/Recursion.pde rename to processing/mode/examples/Basics/Structure/Recursion/Recursion.pde diff --git a/examples/Basics/Structure/Recursion2/Recursion2.pde b/processing/mode/examples/Basics/Structure/Recursion2/Recursion2.pde similarity index 100% rename from examples/Basics/Structure/Recursion2/Recursion2.pde rename to processing/mode/examples/Basics/Structure/Recursion2/Recursion2.pde diff --git a/examples/Basics/Structure/Redraw/Redraw.pde b/processing/mode/examples/Basics/Structure/Redraw/Redraw.pde similarity index 100% rename from examples/Basics/Structure/Redraw/Redraw.pde rename to processing/mode/examples/Basics/Structure/Redraw/Redraw.pde diff --git a/examples/Basics/Structure/SetupDraw/SetupDraw.pde b/processing/mode/examples/Basics/Structure/SetupDraw/SetupDraw.pde similarity index 100% rename from examples/Basics/Structure/SetupDraw/SetupDraw.pde rename to processing/mode/examples/Basics/Structure/SetupDraw/SetupDraw.pde diff --git a/examples/Basics/Structure/StatementsComments/StatementsComments.pde b/processing/mode/examples/Basics/Structure/StatementsComments/StatementsComments.pde similarity index 100% rename from examples/Basics/Structure/StatementsComments/StatementsComments.pde rename to processing/mode/examples/Basics/Structure/StatementsComments/StatementsComments.pde diff --git a/examples/Basics/Structure/WidthHeight/WidthHeight.pde b/processing/mode/examples/Basics/Structure/WidthHeight/WidthHeight.pde similarity index 100% rename from examples/Basics/Structure/WidthHeight/WidthHeight.pde rename to processing/mode/examples/Basics/Structure/WidthHeight/WidthHeight.pde diff --git a/examples/Basics/Transform/Arm/Arm.pde b/processing/mode/examples/Basics/Transform/Arm/Arm.pde similarity index 100% rename from examples/Basics/Transform/Arm/Arm.pde rename to processing/mode/examples/Basics/Transform/Arm/Arm.pde diff --git a/examples/Basics/Transform/Rotate/Rotate.pde b/processing/mode/examples/Basics/Transform/Rotate/Rotate.pde similarity index 100% rename from examples/Basics/Transform/Rotate/Rotate.pde rename to processing/mode/examples/Basics/Transform/Rotate/Rotate.pde diff --git a/examples/Basics/Transform/RotatePushPop/RotatePushPop.pde b/processing/mode/examples/Basics/Transform/RotatePushPop/RotatePushPop.pde similarity index 100% rename from examples/Basics/Transform/RotatePushPop/RotatePushPop.pde rename to processing/mode/examples/Basics/Transform/RotatePushPop/RotatePushPop.pde diff --git a/examples/Basics/Transform/RotateXY/RotateXY.pde b/processing/mode/examples/Basics/Transform/RotateXY/RotateXY.pde similarity index 100% rename from examples/Basics/Transform/RotateXY/RotateXY.pde rename to processing/mode/examples/Basics/Transform/RotateXY/RotateXY.pde diff --git a/examples/Basics/Transform/Scale/Scale.pde b/processing/mode/examples/Basics/Transform/Scale/Scale.pde similarity index 100% rename from examples/Basics/Transform/Scale/Scale.pde rename to processing/mode/examples/Basics/Transform/Scale/Scale.pde diff --git a/examples/Basics/Transform/Translate/Translate.pde b/processing/mode/examples/Basics/Transform/Translate/Translate.pde similarity index 100% rename from examples/Basics/Transform/Translate/Translate.pde rename to processing/mode/examples/Basics/Transform/Translate/Translate.pde diff --git a/examples/Basics/Transform/TriangleFlower/TriangleFlower.pde b/processing/mode/examples/Basics/Transform/TriangleFlower/TriangleFlower.pde similarity index 100% rename from examples/Basics/Transform/TriangleFlower/TriangleFlower.pde rename to processing/mode/examples/Basics/Transform/TriangleFlower/TriangleFlower.pde diff --git a/examples/Basics/Typography/Letters/Letters.pde b/processing/mode/examples/Basics/Typography/Letters/Letters.pde similarity index 100% rename from examples/Basics/Typography/Letters/Letters.pde rename to processing/mode/examples/Basics/Typography/Letters/Letters.pde diff --git a/examples/Basics/Typography/Letters/data/CourierNew36.vlw b/processing/mode/examples/Basics/Typography/Letters/data/CourierNew36.vlw similarity index 100% rename from examples/Basics/Typography/Letters/data/CourierNew36.vlw rename to processing/mode/examples/Basics/Typography/Letters/data/CourierNew36.vlw diff --git a/examples/Basics/Typography/Words/Words.pde b/processing/mode/examples/Basics/Typography/Words/Words.pde similarity index 100% rename from examples/Basics/Typography/Words/Words.pde rename to processing/mode/examples/Basics/Typography/Words/Words.pde diff --git a/examples/Basics/Typography/Words/data/Ziggurat-HTF-Black-32.vlw b/processing/mode/examples/Basics/Typography/Words/data/Ziggurat-HTF-Black-32.vlw similarity index 100% rename from examples/Basics/Typography/Words/data/Ziggurat-HTF-Black-32.vlw rename to processing/mode/examples/Basics/Typography/Words/data/Ziggurat-HTF-Black-32.vlw diff --git a/examples/Basics/Web/EmbeddedLinks/EmbeddedLinks.pde b/processing/mode/examples/Basics/Web/EmbeddedLinks/EmbeddedLinks.pde similarity index 100% rename from examples/Basics/Web/EmbeddedLinks/EmbeddedLinks.pde rename to processing/mode/examples/Basics/Web/EmbeddedLinks/EmbeddedLinks.pde diff --git a/examples/Basics/Web/LoadingImages/LoadingImages.pde b/processing/mode/examples/Basics/Web/LoadingImages/LoadingImages.pde similarity index 100% rename from examples/Basics/Web/LoadingImages/LoadingImages.pde rename to processing/mode/examples/Basics/Web/LoadingImages/LoadingImages.pde diff --git a/examples/Demos/Graphics/Particles/Particle.pde b/processing/mode/examples/Demos/Graphics/Particles/Particle.pde similarity index 100% rename from examples/Demos/Graphics/Particles/Particle.pde rename to processing/mode/examples/Demos/Graphics/Particles/Particle.pde diff --git a/examples/Demos/Graphics/Particles/ParticleSystem.pde b/processing/mode/examples/Demos/Graphics/Particles/ParticleSystem.pde similarity index 100% rename from examples/Demos/Graphics/Particles/ParticleSystem.pde rename to processing/mode/examples/Demos/Graphics/Particles/ParticleSystem.pde diff --git a/examples/Demos/Graphics/Particles/Particles.pde b/processing/mode/examples/Demos/Graphics/Particles/Particles.pde similarity index 100% rename from examples/Demos/Graphics/Particles/Particles.pde rename to processing/mode/examples/Demos/Graphics/Particles/Particles.pde diff --git a/examples/Demos/Graphics/Particles/data/sprite.png b/processing/mode/examples/Demos/Graphics/Particles/data/sprite.png similarity index 100% rename from examples/Demos/Graphics/Particles/data/sprite.png rename to processing/mode/examples/Demos/Graphics/Particles/data/sprite.png diff --git a/examples/Demos/Graphics/Patch/Patch.pde b/processing/mode/examples/Demos/Graphics/Patch/Patch.pde similarity index 100% rename from examples/Demos/Graphics/Patch/Patch.pde rename to processing/mode/examples/Demos/Graphics/Patch/Patch.pde diff --git a/examples/Demos/Graphics/Planets/Perlin.pde b/processing/mode/examples/Demos/Graphics/Planets/Perlin.pde similarity index 100% rename from examples/Demos/Graphics/Planets/Perlin.pde rename to processing/mode/examples/Demos/Graphics/Planets/Perlin.pde diff --git a/examples/Demos/Graphics/Planets/Planets.pde b/processing/mode/examples/Demos/Graphics/Planets/Planets.pde similarity index 100% rename from examples/Demos/Graphics/Planets/Planets.pde rename to processing/mode/examples/Demos/Graphics/Planets/Planets.pde diff --git a/examples/Demos/Graphics/Planets/data/mercury.jpg b/processing/mode/examples/Demos/Graphics/Planets/data/mercury.jpg similarity index 100% rename from examples/Demos/Graphics/Planets/data/mercury.jpg rename to processing/mode/examples/Demos/Graphics/Planets/data/mercury.jpg diff --git a/examples/Demos/Graphics/Planets/data/planet.jpg b/processing/mode/examples/Demos/Graphics/Planets/data/planet.jpg similarity index 100% rename from examples/Demos/Graphics/Planets/data/planet.jpg rename to processing/mode/examples/Demos/Graphics/Planets/data/planet.jpg diff --git a/examples/Demos/Graphics/Planets/data/starfield.jpg b/processing/mode/examples/Demos/Graphics/Planets/data/starfield.jpg similarity index 100% rename from examples/Demos/Graphics/Planets/data/starfield.jpg rename to processing/mode/examples/Demos/Graphics/Planets/data/starfield.jpg diff --git a/examples/Demos/Graphics/Planets/data/sun.jpg b/processing/mode/examples/Demos/Graphics/Planets/data/sun.jpg similarity index 100% rename from examples/Demos/Graphics/Planets/data/sun.jpg rename to processing/mode/examples/Demos/Graphics/Planets/data/sun.jpg diff --git a/examples/Demos/Graphics/Ribbons/ArcBall.pde b/processing/mode/examples/Demos/Graphics/Ribbons/ArcBall.pde similarity index 100% rename from examples/Demos/Graphics/Ribbons/ArcBall.pde rename to processing/mode/examples/Demos/Graphics/Ribbons/ArcBall.pde diff --git a/examples/Demos/Graphics/Ribbons/BSpline.pde b/processing/mode/examples/Demos/Graphics/Ribbons/BSpline.pde similarity index 100% rename from examples/Demos/Graphics/Ribbons/BSpline.pde rename to processing/mode/examples/Demos/Graphics/Ribbons/BSpline.pde diff --git a/examples/Demos/Graphics/Ribbons/Geometry.pde b/processing/mode/examples/Demos/Graphics/Ribbons/Geometry.pde similarity index 100% rename from examples/Demos/Graphics/Ribbons/Geometry.pde rename to processing/mode/examples/Demos/Graphics/Ribbons/Geometry.pde diff --git a/examples/Demos/Graphics/Ribbons/PDB.pde b/processing/mode/examples/Demos/Graphics/Ribbons/PDB.pde similarity index 100% rename from examples/Demos/Graphics/Ribbons/PDB.pde rename to processing/mode/examples/Demos/Graphics/Ribbons/PDB.pde diff --git a/examples/Demos/Graphics/Ribbons/Ribbons.pde b/processing/mode/examples/Demos/Graphics/Ribbons/Ribbons.pde similarity index 100% rename from examples/Demos/Graphics/Ribbons/Ribbons.pde rename to processing/mode/examples/Demos/Graphics/Ribbons/Ribbons.pde diff --git a/examples/Demos/Graphics/Ribbons/data/1CBS.pdb b/processing/mode/examples/Demos/Graphics/Ribbons/data/1CBS.pdb similarity index 100% rename from examples/Demos/Graphics/Ribbons/data/1CBS.pdb rename to processing/mode/examples/Demos/Graphics/Ribbons/data/1CBS.pdb diff --git a/examples/Demos/Graphics/Ribbons/data/2POR.pdb b/processing/mode/examples/Demos/Graphics/Ribbons/data/2POR.pdb similarity index 100% rename from examples/Demos/Graphics/Ribbons/data/2POR.pdb rename to processing/mode/examples/Demos/Graphics/Ribbons/data/2POR.pdb diff --git a/examples/Demos/Graphics/Ribbons/data/4HHB.pdb b/processing/mode/examples/Demos/Graphics/Ribbons/data/4HHB.pdb similarity index 100% rename from examples/Demos/Graphics/Ribbons/data/4HHB.pdb rename to processing/mode/examples/Demos/Graphics/Ribbons/data/4HHB.pdb diff --git a/examples/Demos/Graphics/RotatingArcs/RotatingArcs.pde b/processing/mode/examples/Demos/Graphics/RotatingArcs/RotatingArcs.pde similarity index 100% rename from examples/Demos/Graphics/RotatingArcs/RotatingArcs.pde rename to processing/mode/examples/Demos/Graphics/RotatingArcs/RotatingArcs.pde diff --git a/examples/Demos/Graphics/Trefoil/Surface.pde b/processing/mode/examples/Demos/Graphics/Trefoil/Surface.pde similarity index 100% rename from examples/Demos/Graphics/Trefoil/Surface.pde rename to processing/mode/examples/Demos/Graphics/Trefoil/Surface.pde diff --git a/examples/Demos/Graphics/Trefoil/Trefoil.pde b/processing/mode/examples/Demos/Graphics/Trefoil/Trefoil.pde similarity index 100% rename from examples/Demos/Graphics/Trefoil/Trefoil.pde rename to processing/mode/examples/Demos/Graphics/Trefoil/Trefoil.pde diff --git a/examples/Demos/Graphics/Trefoil/data/particle.png b/processing/mode/examples/Demos/Graphics/Trefoil/data/particle.png similarity index 100% rename from examples/Demos/Graphics/Trefoil/data/particle.png rename to processing/mode/examples/Demos/Graphics/Trefoil/data/particle.png diff --git a/examples/Demos/Graphics/Wiggling/Wiggling.pde b/processing/mode/examples/Demos/Graphics/Wiggling/Wiggling.pde similarity index 100% rename from examples/Demos/Graphics/Wiggling/Wiggling.pde rename to processing/mode/examples/Demos/Graphics/Wiggling/Wiggling.pde diff --git a/examples/Demos/Graphics/Yellowtail/Gesture.pde b/processing/mode/examples/Demos/Graphics/Yellowtail/Gesture.pde similarity index 100% rename from examples/Demos/Graphics/Yellowtail/Gesture.pde rename to processing/mode/examples/Demos/Graphics/Yellowtail/Gesture.pde diff --git a/examples/Demos/Graphics/Yellowtail/Polygon.pde b/processing/mode/examples/Demos/Graphics/Yellowtail/Polygon.pde similarity index 100% rename from examples/Demos/Graphics/Yellowtail/Polygon.pde rename to processing/mode/examples/Demos/Graphics/Yellowtail/Polygon.pde diff --git a/examples/Demos/Graphics/Yellowtail/Vec3f.pde b/processing/mode/examples/Demos/Graphics/Yellowtail/Vec3f.pde similarity index 100% rename from examples/Demos/Graphics/Yellowtail/Vec3f.pde rename to processing/mode/examples/Demos/Graphics/Yellowtail/Vec3f.pde diff --git a/examples/Demos/Graphics/Yellowtail/Yellowtail.pde b/processing/mode/examples/Demos/Graphics/Yellowtail/Yellowtail.pde similarity index 100% rename from examples/Demos/Graphics/Yellowtail/Yellowtail.pde rename to processing/mode/examples/Demos/Graphics/Yellowtail/Yellowtail.pde diff --git a/examples/Demos/Performance/CubicGridImmediate/CubicGridImmediate.pde b/processing/mode/examples/Demos/Performance/CubicGridImmediate/CubicGridImmediate.pde similarity index 100% rename from examples/Demos/Performance/CubicGridImmediate/CubicGridImmediate.pde rename to processing/mode/examples/Demos/Performance/CubicGridImmediate/CubicGridImmediate.pde diff --git a/examples/Demos/Performance/CubicGridRetained/CubicGridRetained.pde b/processing/mode/examples/Demos/Performance/CubicGridRetained/CubicGridRetained.pde similarity index 100% rename from examples/Demos/Performance/CubicGridRetained/CubicGridRetained.pde rename to processing/mode/examples/Demos/Performance/CubicGridRetained/CubicGridRetained.pde diff --git a/examples/Demos/Performance/DynamicParticlesImmediate/DynamicParticlesImmediate.pde b/processing/mode/examples/Demos/Performance/DynamicParticlesImmediate/DynamicParticlesImmediate.pde similarity index 100% rename from examples/Demos/Performance/DynamicParticlesImmediate/DynamicParticlesImmediate.pde rename to processing/mode/examples/Demos/Performance/DynamicParticlesImmediate/DynamicParticlesImmediate.pde diff --git a/examples/Demos/Performance/DynamicParticlesImmediate/data/sprite.png b/processing/mode/examples/Demos/Performance/DynamicParticlesImmediate/data/sprite.png similarity index 100% rename from examples/Demos/Performance/DynamicParticlesImmediate/data/sprite.png rename to processing/mode/examples/Demos/Performance/DynamicParticlesImmediate/data/sprite.png diff --git a/examples/Demos/Performance/DynamicParticlesRetained/DynamicParticlesRetained.pde b/processing/mode/examples/Demos/Performance/DynamicParticlesRetained/DynamicParticlesRetained.pde similarity index 100% rename from examples/Demos/Performance/DynamicParticlesRetained/DynamicParticlesRetained.pde rename to processing/mode/examples/Demos/Performance/DynamicParticlesRetained/DynamicParticlesRetained.pde diff --git a/examples/Demos/Performance/DynamicParticlesRetained/data/sprite.png b/processing/mode/examples/Demos/Performance/DynamicParticlesRetained/data/sprite.png similarity index 100% rename from examples/Demos/Performance/DynamicParticlesRetained/data/sprite.png rename to processing/mode/examples/Demos/Performance/DynamicParticlesRetained/data/sprite.png diff --git a/examples/Demos/Performance/Esfera/Esfera.pde b/processing/mode/examples/Demos/Performance/Esfera/Esfera.pde similarity index 100% rename from examples/Demos/Performance/Esfera/Esfera.pde rename to processing/mode/examples/Demos/Performance/Esfera/Esfera.pde diff --git a/examples/Demos/Performance/LineRendering/LineRendering.pde b/processing/mode/examples/Demos/Performance/LineRendering/LineRendering.pde similarity index 100% rename from examples/Demos/Performance/LineRendering/LineRendering.pde rename to processing/mode/examples/Demos/Performance/LineRendering/LineRendering.pde diff --git a/processing/mode/examples/Demos/Performance/P2DXDemo/P2DXDemo.pde b/processing/mode/examples/Demos/Performance/P2DXDemo/P2DXDemo.pde new file mode 100644 index 000000000..c929f5a35 --- /dev/null +++ b/processing/mode/examples/Demos/Performance/P2DXDemo/P2DXDemo.pde @@ -0,0 +1,60 @@ +int join = MITER; +int cap = SQUARE; + +boolean premultiply = true; + +float dev = 10; //deviation + +//change these parameters to benchmark various things +int unit = 10; +//line, triangle, rect, ellipse, point +int[] amount = { 20, 15, 10, 5, 40 }; + +void setup() { + fullScreen(P2DX); + strokeCap(cap); + strokeJoin(join); + PGraphics2DX.premultiplyMatrices = premultiply; + + textFont(createFont("SansSerif", 15 * displayDensity)); +} + +public void draw() { + background(255); + + strokeWeight(2 * displayDensity); + stroke(0); + fill(200); + + for (int i = 0; i < amount[0]*unit; ++i) { + float x = random(width); + float y = random(height); + line(x, y, x + random(-dev, dev), y + random(-dev, dev)); + } + + for (int i = 0; i < amount[1]*unit; ++i) { + float x = random(width); + float y = random(height); + triangle(x, y, + x + random(-dev*2, dev*2), y + random(-dev*2, dev*2), + x + random(-dev*2, dev*2), y + random(-dev*2, dev*2)); + } + + for (int i = 0; i < amount[2]*unit; ++i) { + rect(random(width), random(height), random(dev), random(dev)); + } + + for (int i = 0; i < amount[3]*unit; ++i) { + ellipse(random(width), random(height), random(dev*2), random(dev*2)); + } + + for (int i = 0; i < amount[4]*unit; ++i) { + point(random(width), random(height)); + } + + //large ellipse to test smoothness of outline + ellipse(width/2, height/2, width/2, height/4); + + fill(255, 0, 0); + text((int) frameRate + " fps", 30, 30); +} \ No newline at end of file diff --git a/examples/Demos/Performance/QuadRendering/QuadRendering.pde b/processing/mode/examples/Demos/Performance/QuadRendering/QuadRendering.pde similarity index 100% rename from examples/Demos/Performance/QuadRendering/QuadRendering.pde rename to processing/mode/examples/Demos/Performance/QuadRendering/QuadRendering.pde diff --git a/examples/Demos/Performance/StaticParticlesImmediate/StaticParticlesImmediate.pde b/processing/mode/examples/Demos/Performance/StaticParticlesImmediate/StaticParticlesImmediate.pde similarity index 100% rename from examples/Demos/Performance/StaticParticlesImmediate/StaticParticlesImmediate.pde rename to processing/mode/examples/Demos/Performance/StaticParticlesImmediate/StaticParticlesImmediate.pde diff --git a/examples/Demos/Performance/StaticParticlesImmediate/data/sprite.png b/processing/mode/examples/Demos/Performance/StaticParticlesImmediate/data/sprite.png similarity index 100% rename from examples/Demos/Performance/StaticParticlesImmediate/data/sprite.png rename to processing/mode/examples/Demos/Performance/StaticParticlesImmediate/data/sprite.png diff --git a/examples/Demos/Performance/StaticParticlesRetained/StaticParticlesRetained.pde b/processing/mode/examples/Demos/Performance/StaticParticlesRetained/StaticParticlesRetained.pde similarity index 100% rename from examples/Demos/Performance/StaticParticlesRetained/StaticParticlesRetained.pde rename to processing/mode/examples/Demos/Performance/StaticParticlesRetained/StaticParticlesRetained.pde diff --git a/examples/Demos/Performance/StaticParticlesRetained/data/sprite.png b/processing/mode/examples/Demos/Performance/StaticParticlesRetained/data/sprite.png similarity index 100% rename from examples/Demos/Performance/StaticParticlesRetained/data/sprite.png rename to processing/mode/examples/Demos/Performance/StaticParticlesRetained/data/sprite.png diff --git a/examples/Demos/Performance/TextRendering/TextRendering.pde b/processing/mode/examples/Demos/Performance/TextRendering/TextRendering.pde similarity index 100% rename from examples/Demos/Performance/TextRendering/TextRendering.pde rename to processing/mode/examples/Demos/Performance/TextRendering/TextRendering.pde diff --git a/examples/Demos/Tests/NoBackgroundTest/NoBackgroundTest.pde b/processing/mode/examples/Demos/Tests/NoBackgroundTest/NoBackgroundTest.pde similarity index 100% rename from examples/Demos/Tests/NoBackgroundTest/NoBackgroundTest.pde rename to processing/mode/examples/Demos/Tests/NoBackgroundTest/NoBackgroundTest.pde diff --git a/examples/Demos/Tests/OffscreenTest/OffscreenTest.pde b/processing/mode/examples/Demos/Tests/OffscreenTest/OffscreenTest.pde similarity index 100% rename from examples/Demos/Tests/OffscreenTest/OffscreenTest.pde rename to processing/mode/examples/Demos/Tests/OffscreenTest/OffscreenTest.pde diff --git a/examples/Demos/Tests/RedrawTest/RedrawTest.pde b/processing/mode/examples/Demos/Tests/RedrawTest/RedrawTest.pde similarity index 100% rename from examples/Demos/Tests/RedrawTest/RedrawTest.pde rename to processing/mode/examples/Demos/Tests/RedrawTest/RedrawTest.pde diff --git a/examples/Sensors/Accelerometer/Accelerometer.pde b/processing/mode/examples/Sensors/Accelerometer/Accelerometer.pde similarity index 99% rename from examples/Sensors/Accelerometer/Accelerometer.pde rename to processing/mode/examples/Sensors/Accelerometer/Accelerometer.pde index e4524fc49..56e346d4a 100644 --- a/examples/Sensors/Accelerometer/Accelerometer.pde +++ b/processing/mode/examples/Sensors/Accelerometer/Accelerometer.pde @@ -46,4 +46,4 @@ public void accelerationEvent(float x, float y, float z) { ay = y; az = z; redraw(); -} +} \ No newline at end of file diff --git a/examples/Sensors/Accelerometer/AccelerometerManager.java b/processing/mode/examples/Sensors/Accelerometer/AccelerometerManager.java similarity index 93% rename from examples/Sensors/Accelerometer/AccelerometerManager.java rename to processing/mode/examples/Sensors/Accelerometer/AccelerometerManager.java index b4340fc2b..1952ea8c5 100644 --- a/examples/Sensors/Accelerometer/AccelerometerManager.java +++ b/processing/mode/examples/Sensors/Accelerometer/AccelerometerManager.java @@ -1,3 +1,5 @@ +import processing.core.PApplet; + import java.lang.reflect.*; import java.util.List; @@ -32,11 +34,12 @@ public class AccelerometerManager { /** indicates whether or not Accelerometer Sensor is running */ private boolean running = false; - Context context; - + PApplet parent; + Context context; - public AccelerometerManager(Context parent) { - this.context = parent; + public AccelerometerManager(PApplet parent) { + this.parent = parent; + this.context = parent.getActivity(); try { shakeEventMethod = @@ -57,8 +60,8 @@ public AccelerometerManager(Context parent) { } - public AccelerometerManager(Context context, int threshold, int interval) { - this(context); + public AccelerometerManager(PApplet parent, int threshold, int interval) { + this(parent); this.threshold = threshold; this.interval = interval; } @@ -211,7 +214,7 @@ public void onSensorChanged(SensorEvent event) { // listener.onShake(force); if (shakeEventMethod != null) { try { - shakeEventMethod.invoke(context, new Object[] { new Float(force) }); + shakeEventMethod.invoke(parent, new Object[] { new Float(force) }); } catch (Exception e) { e.printStackTrace(); shakeEventMethod = null; @@ -230,7 +233,7 @@ public void onSensorChanged(SensorEvent event) { // listener.onAccelerationChanged(x, y, z); if (accelerationEventMethod != null) { try { - accelerationEventMethod.invoke(context, new Object[] { x, y, z }); + accelerationEventMethod.invoke(parent, new Object[] { x, y, z }); } catch (Exception e) { e.printStackTrace(); accelerationEventMethod = null; @@ -238,5 +241,4 @@ public void onSensorChanged(SensorEvent event) { } } }; -} - +} \ No newline at end of file diff --git a/examples/Sensors/Compass/Compass.pde b/processing/mode/examples/Sensors/Compass/Compass.pde similarity index 100% rename from examples/Sensors/Compass/Compass.pde rename to processing/mode/examples/Sensors/Compass/Compass.pde diff --git a/examples/Sensors/Compass/CompassManager.java b/processing/mode/examples/Sensors/Compass/CompassManager.java similarity index 90% rename from examples/Sensors/Compass/CompassManager.java rename to processing/mode/examples/Sensors/Compass/CompassManager.java index c45238f8d..f3cd99d71 100644 --- a/examples/Sensors/Compass/CompassManager.java +++ b/processing/mode/examples/Sensors/Compass/CompassManager.java @@ -1,3 +1,5 @@ +import processing.core.PApplet; + import java.lang.reflect.*; import java.util.List; @@ -18,11 +20,12 @@ public class CompassManager { private Boolean supported; private boolean running = false; + PApplet parent; Context context; - - - public CompassManager(Context parent) { - this.context = parent; + + public CompassManager(PApplet parent) { + this.parent = parent; + this.context = parent.getActivity(); try { compassEventMethod = @@ -118,7 +121,7 @@ public void onSensorChanged(SensorEvent event) { if (compassEventMethod != null) { try { - compassEventMethod.invoke(context, new Object[] { x, y, z }); + compassEventMethod.invoke(parent, new Object[] { x, y, z }); } catch (Exception e) { e.printStackTrace(); compassEventMethod = null; @@ -127,7 +130,7 @@ public void onSensorChanged(SensorEvent event) { if (directionEventMethod != null) { try { - directionEventMethod.invoke(context, new Object[] { (float) (-x * Math.PI / 180) }); + directionEventMethod.invoke(parent, new Object[] { (float) (-x * Math.PI / 180) }); } catch (Exception e) { e.printStackTrace(); directionEventMethod = null; @@ -135,5 +138,4 @@ public void onSensorChanged(SensorEvent event) { } } }; -} - +} \ No newline at end of file diff --git a/examples/Topics/Advanced Data/ArrayListClass/ArrayListClass.pde b/processing/mode/examples/Topics/Advanced Data/ArrayListClass/ArrayListClass.pde similarity index 100% rename from examples/Topics/Advanced Data/ArrayListClass/ArrayListClass.pde rename to processing/mode/examples/Topics/Advanced Data/ArrayListClass/ArrayListClass.pde diff --git a/examples/Topics/Advanced Data/ArrayListClass/Ball.pde b/processing/mode/examples/Topics/Advanced Data/ArrayListClass/Ball.pde similarity index 100% rename from examples/Topics/Advanced Data/ArrayListClass/Ball.pde rename to processing/mode/examples/Topics/Advanced Data/ArrayListClass/Ball.pde diff --git a/examples/Topics/Advanced Data/DirectoryList/DirectoryList.pde b/processing/mode/examples/Topics/Advanced Data/DirectoryList/DirectoryList.pde similarity index 100% rename from examples/Topics/Advanced Data/DirectoryList/DirectoryList.pde rename to processing/mode/examples/Topics/Advanced Data/DirectoryList/DirectoryList.pde diff --git a/examples/Topics/Advanced Data/HashMapClass/HashMapClass.pde b/processing/mode/examples/Topics/Advanced Data/HashMapClass/HashMapClass.pde similarity index 100% rename from examples/Topics/Advanced Data/HashMapClass/HashMapClass.pde rename to processing/mode/examples/Topics/Advanced Data/HashMapClass/HashMapClass.pde diff --git a/examples/Topics/Advanced Data/HashMapClass/Word.pde b/processing/mode/examples/Topics/Advanced Data/HashMapClass/Word.pde similarity index 100% rename from examples/Topics/Advanced Data/HashMapClass/Word.pde rename to processing/mode/examples/Topics/Advanced Data/HashMapClass/Word.pde diff --git a/examples/Topics/Advanced Data/HashMapClass/data/dracula.txt b/processing/mode/examples/Topics/Advanced Data/HashMapClass/data/dracula.txt similarity index 100% rename from examples/Topics/Advanced Data/HashMapClass/data/dracula.txt rename to processing/mode/examples/Topics/Advanced Data/HashMapClass/data/dracula.txt diff --git a/examples/Topics/Advanced Data/HashMapClass/data/hamlet.txt b/processing/mode/examples/Topics/Advanced Data/HashMapClass/data/hamlet.txt similarity index 100% rename from examples/Topics/Advanced Data/HashMapClass/data/hamlet.txt rename to processing/mode/examples/Topics/Advanced Data/HashMapClass/data/hamlet.txt diff --git a/examples/Topics/Animation/AnimatedSprite/AnimatedSprite.pde b/processing/mode/examples/Topics/Animation/AnimatedSprite/AnimatedSprite.pde similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/AnimatedSprite.pde rename to processing/mode/examples/Topics/Animation/AnimatedSprite/AnimatedSprite.pde diff --git a/examples/Topics/Animation/AnimatedSprite/Animation.pde b/processing/mode/examples/Topics/Animation/AnimatedSprite/Animation.pde similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/Animation.pde rename to processing/mode/examples/Topics/Animation/AnimatedSprite/Animation.pde diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0000.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0000.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0000.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0000.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0001.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0001.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0001.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0001.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0002.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0002.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0002.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0002.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0003.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0003.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0003.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0003.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0004.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0004.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0004.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0004.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0005.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0005.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0005.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0005.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0006.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0006.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0006.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0006.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0007.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0007.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0007.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0007.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0008.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0008.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0008.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0008.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0009.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0009.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0009.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0009.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0010.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0010.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0010.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0010.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0011.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0011.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0011.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0011.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0012.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0012.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0012.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0012.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0013.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0013.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0013.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0013.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0014.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0014.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0014.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0014.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0015.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0015.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0015.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0015.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0016.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0016.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0016.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0016.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0017.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0017.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0017.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0017.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0018.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0018.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0018.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0018.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0019.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0019.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0019.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0019.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0020.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0020.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0020.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0020.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0021.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0021.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0021.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0021.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0022.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0022.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0022.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0022.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0023.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0023.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0023.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0023.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0024.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0024.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0024.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0024.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0025.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0025.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0025.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0025.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0026.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0026.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0026.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0026.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0027.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0027.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0027.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0027.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0028.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0028.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0028.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0028.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0029.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0029.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0029.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0029.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0030.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0030.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0030.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0030.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0031.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0031.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0031.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0031.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0032.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0032.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0032.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0032.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0033.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0033.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0033.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0033.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0034.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0034.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0034.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0034.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0035.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0035.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0035.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0035.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0036.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0036.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0036.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0036.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0037.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0037.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0037.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Shifty_0037.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0000.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0000.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0000.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0000.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0001.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0001.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0001.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0001.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0002.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0002.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0002.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0002.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0003.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0003.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0003.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0003.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0004.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0004.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0004.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0004.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0005.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0005.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0005.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0005.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0006.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0006.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0006.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0006.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0007.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0007.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0007.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0007.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0008.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0008.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0008.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0008.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0009.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0009.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0009.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0009.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0010.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0010.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0010.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0010.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0011.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0011.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0011.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0011.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0012.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0012.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0012.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0012.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0013.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0013.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0013.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0013.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0014.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0014.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0014.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0014.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0015.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0015.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0015.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0015.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0016.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0016.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0016.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0016.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0017.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0017.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0017.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0017.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0018.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0018.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0018.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0018.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0019.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0019.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0019.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0019.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0020.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0020.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0020.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0020.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0021.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0021.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0021.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0021.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0022.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0022.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0022.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0022.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0023.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0023.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0023.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0023.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0024.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0024.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0024.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0024.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0025.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0025.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0025.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0025.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0026.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0026.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0026.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0026.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0027.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0027.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0027.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0027.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0028.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0028.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0028.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0028.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0029.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0029.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0029.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0029.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0030.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0030.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0030.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0030.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0031.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0031.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0031.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0031.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0032.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0032.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0032.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0032.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0033.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0033.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0033.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0033.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0034.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0034.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0034.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0034.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0035.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0035.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0035.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0035.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0036.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0036.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0036.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0036.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0037.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0037.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0037.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0037.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0038.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0038.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0038.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0038.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0039.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0039.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0039.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0039.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0040.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0040.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0040.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0040.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0041.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0041.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0041.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0041.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0042.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0042.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0042.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0042.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0043.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0043.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0043.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0043.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0044.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0044.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0044.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0044.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0045.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0045.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0045.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0045.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0046.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0046.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0046.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0046.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0047.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0047.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0047.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0047.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0048.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0048.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0048.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0048.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0049.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0049.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0049.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0049.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0050.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0050.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0050.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0050.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0051.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0051.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0051.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0051.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0052.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0052.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0052.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0052.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0053.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0053.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0053.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0053.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0054.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0054.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0054.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0054.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0055.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0055.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0055.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0055.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0056.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0056.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0056.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0056.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0057.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0057.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0057.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0057.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0058.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0058.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0058.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0058.gif diff --git a/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0059.gif b/processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0059.gif similarity index 100% rename from examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0059.gif rename to processing/mode/examples/Topics/Animation/AnimatedSprite/data/PT_Teddy_0059.gif diff --git a/examples/Topics/Animation/Sequential/Sequential.pde b/processing/mode/examples/Topics/Animation/Sequential/Sequential.pde similarity index 100% rename from examples/Topics/Animation/Sequential/Sequential.pde rename to processing/mode/examples/Topics/Animation/Sequential/Sequential.pde diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0000.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0000.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0000.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0000.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0001.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0001.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0001.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0001.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0002.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0002.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0002.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0002.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0003.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0003.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0003.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0003.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0004.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0004.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0004.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0004.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0005.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0005.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0005.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0005.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0006.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0006.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0006.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0006.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0007.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0007.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0007.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0007.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0008.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0008.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0008.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0008.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0009.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0009.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0009.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0009.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0010.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0010.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0010.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0010.gif diff --git a/examples/Topics/Animation/Sequential/data/PT_anim0011.gif b/processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0011.gif similarity index 100% rename from examples/Topics/Animation/Sequential/data/PT_anim0011.gif rename to processing/mode/examples/Topics/Animation/Sequential/data/PT_anim0011.gif diff --git a/examples/Topics/Cellular Automata/Conway/Conway.pde b/processing/mode/examples/Topics/Cellular Automata/Conway/Conway.pde similarity index 100% rename from examples/Topics/Cellular Automata/Conway/Conway.pde rename to processing/mode/examples/Topics/Cellular Automata/Conway/Conway.pde diff --git a/examples/Topics/Cellular Automata/Spore1/Spore1.pde b/processing/mode/examples/Topics/Cellular Automata/Spore1/Spore1.pde similarity index 100% rename from examples/Topics/Cellular Automata/Spore1/Spore1.pde rename to processing/mode/examples/Topics/Cellular Automata/Spore1/Spore1.pde diff --git a/examples/Topics/Cellular Automata/Spore2/Spore2.pde b/processing/mode/examples/Topics/Cellular Automata/Spore2/Spore2.pde similarity index 100% rename from examples/Topics/Cellular Automata/Spore2/Spore2.pde rename to processing/mode/examples/Topics/Cellular Automata/Spore2/Spore2.pde diff --git a/examples/Topics/Cellular Automata/Wolfram/CA.pde b/processing/mode/examples/Topics/Cellular Automata/Wolfram/CA.pde similarity index 100% rename from examples/Topics/Cellular Automata/Wolfram/CA.pde rename to processing/mode/examples/Topics/Cellular Automata/Wolfram/CA.pde diff --git a/examples/Topics/Cellular Automata/Wolfram/Wolfram.pde b/processing/mode/examples/Topics/Cellular Automata/Wolfram/Wolfram.pde similarity index 100% rename from examples/Topics/Cellular Automata/Wolfram/Wolfram.pde rename to processing/mode/examples/Topics/Cellular Automata/Wolfram/Wolfram.pde diff --git a/examples/Topics/Create Shapes/BeginEndContour/BeginEndContour.pde b/processing/mode/examples/Topics/Create Shapes/BeginEndContour/BeginEndContour.pde similarity index 100% rename from examples/Topics/Create Shapes/BeginEndContour/BeginEndContour.pde rename to processing/mode/examples/Topics/Create Shapes/BeginEndContour/BeginEndContour.pde diff --git a/examples/Topics/Create Shapes/GroupPShape/GroupPShape.pde b/processing/mode/examples/Topics/Create Shapes/GroupPShape/GroupPShape.pde similarity index 100% rename from examples/Topics/Create Shapes/GroupPShape/GroupPShape.pde rename to processing/mode/examples/Topics/Create Shapes/GroupPShape/GroupPShape.pde diff --git a/examples/Topics/Create Shapes/ParticleSystemPShape/Particle.pde b/processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/Particle.pde similarity index 100% rename from examples/Topics/Create Shapes/ParticleSystemPShape/Particle.pde rename to processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/Particle.pde diff --git a/examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystem.pde b/processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystem.pde similarity index 100% rename from examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystem.pde rename to processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystem.pde diff --git a/examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystemPShape.pde b/processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystemPShape.pde similarity index 100% rename from examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystemPShape.pde rename to processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/ParticleSystemPShape.pde diff --git a/examples/Topics/Create Shapes/ParticleSystemPShape/data/sprite.png b/processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/data/sprite.png similarity index 100% rename from examples/Topics/Create Shapes/ParticleSystemPShape/data/sprite.png rename to processing/mode/examples/Topics/Create Shapes/ParticleSystemPShape/data/sprite.png diff --git a/examples/Topics/Create Shapes/PathPShape/PathPShape.pde b/processing/mode/examples/Topics/Create Shapes/PathPShape/PathPShape.pde similarity index 100% rename from examples/Topics/Create Shapes/PathPShape/PathPShape.pde rename to processing/mode/examples/Topics/Create Shapes/PathPShape/PathPShape.pde diff --git a/examples/Topics/Create Shapes/PolygonPShape/PolygonPShape.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShape/PolygonPShape.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShape/PolygonPShape.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShape/PolygonPShape.pde diff --git a/examples/Topics/Create Shapes/PolygonPShapeOOP/PolygonPShapeOOP.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP/PolygonPShapeOOP.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShapeOOP/PolygonPShapeOOP.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP/PolygonPShapeOOP.pde diff --git a/examples/Topics/Create Shapes/PolygonPShapeOOP/Star.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP/Star.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShapeOOP/Star.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP/Star.pde diff --git a/examples/Topics/Create Shapes/PolygonPShapeOOP2/Polygon.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP2/Polygon.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShapeOOP2/Polygon.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP2/Polygon.pde diff --git a/examples/Topics/Create Shapes/PolygonPShapeOOP2/PolygonPShapeOOP2.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP2/PolygonPShapeOOP2.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShapeOOP2/PolygonPShapeOOP2.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP2/PolygonPShapeOOP2.pde diff --git a/examples/Topics/Create Shapes/PolygonPShapeOOP3/Polygon.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP3/Polygon.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShapeOOP3/Polygon.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP3/Polygon.pde diff --git a/examples/Topics/Create Shapes/PolygonPShapeOOP3/PolygonPShapeOOP3.pde b/processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP3/PolygonPShapeOOP3.pde similarity index 100% rename from examples/Topics/Create Shapes/PolygonPShapeOOP3/PolygonPShapeOOP3.pde rename to processing/mode/examples/Topics/Create Shapes/PolygonPShapeOOP3/PolygonPShapeOOP3.pde diff --git a/examples/Topics/Create Shapes/PrimitivePShape/PrimitivePShape.pde b/processing/mode/examples/Topics/Create Shapes/PrimitivePShape/PrimitivePShape.pde similarity index 100% rename from examples/Topics/Create Shapes/PrimitivePShape/PrimitivePShape.pde rename to processing/mode/examples/Topics/Create Shapes/PrimitivePShape/PrimitivePShape.pde diff --git a/examples/Topics/Create Shapes/WigglePShape/WigglePShape.pde b/processing/mode/examples/Topics/Create Shapes/WigglePShape/WigglePShape.pde similarity index 100% rename from examples/Topics/Create Shapes/WigglePShape/WigglePShape.pde rename to processing/mode/examples/Topics/Create Shapes/WigglePShape/WigglePShape.pde diff --git a/examples/Topics/Create Shapes/WigglePShape/Wiggler.pde b/processing/mode/examples/Topics/Create Shapes/WigglePShape/Wiggler.pde similarity index 100% rename from examples/Topics/Create Shapes/WigglePShape/Wiggler.pde rename to processing/mode/examples/Topics/Create Shapes/WigglePShape/Wiggler.pde diff --git a/examples/Topics/Drawing/Animator/Animator.pde b/processing/mode/examples/Topics/Drawing/Animator/Animator.pde similarity index 100% rename from examples/Topics/Drawing/Animator/Animator.pde rename to processing/mode/examples/Topics/Drawing/Animator/Animator.pde diff --git a/examples/Topics/Drawing/ContinuousLines/ContinuousLines.pde b/processing/mode/examples/Topics/Drawing/ContinuousLines/ContinuousLines.pde similarity index 100% rename from examples/Topics/Drawing/ContinuousLines/ContinuousLines.pde rename to processing/mode/examples/Topics/Drawing/ContinuousLines/ContinuousLines.pde diff --git a/examples/Topics/Drawing/CustomTool/CustomTool.pde b/processing/mode/examples/Topics/Drawing/CustomTool/CustomTool.pde similarity index 100% rename from examples/Topics/Drawing/CustomTool/CustomTool.pde rename to processing/mode/examples/Topics/Drawing/CustomTool/CustomTool.pde diff --git a/examples/Topics/Drawing/CustomTool/data/milan.jpg b/processing/mode/examples/Topics/Drawing/CustomTool/data/milan.jpg similarity index 100% rename from examples/Topics/Drawing/CustomTool/data/milan.jpg rename to processing/mode/examples/Topics/Drawing/CustomTool/data/milan.jpg diff --git a/examples/Topics/Drawing/CustomTool/data/paris.jpg b/processing/mode/examples/Topics/Drawing/CustomTool/data/paris.jpg similarity index 100% rename from examples/Topics/Drawing/CustomTool/data/paris.jpg rename to processing/mode/examples/Topics/Drawing/CustomTool/data/paris.jpg diff --git a/examples/Topics/Drawing/Pattern/Pattern.pde b/processing/mode/examples/Topics/Drawing/Pattern/Pattern.pde similarity index 100% rename from examples/Topics/Drawing/Pattern/Pattern.pde rename to processing/mode/examples/Topics/Drawing/Pattern/Pattern.pde diff --git a/examples/Topics/Drawing/Pulses/Pulses.pde b/processing/mode/examples/Topics/Drawing/Pulses/Pulses.pde similarity index 100% rename from examples/Topics/Drawing/Pulses/Pulses.pde rename to processing/mode/examples/Topics/Drawing/Pulses/Pulses.pde diff --git a/examples/Topics/Drawing/ScribblePlotter/ScribblePlotter.pde b/processing/mode/examples/Topics/Drawing/ScribblePlotter/ScribblePlotter.pde similarity index 100% rename from examples/Topics/Drawing/ScribblePlotter/ScribblePlotter.pde rename to processing/mode/examples/Topics/Drawing/ScribblePlotter/ScribblePlotter.pde diff --git a/examples/Topics/Effects/FireCube/FireCube.pde b/processing/mode/examples/Topics/Effects/FireCube/FireCube.pde similarity index 99% rename from examples/Topics/Effects/FireCube/FireCube.pde rename to processing/mode/examples/Topics/Effects/FireCube/FireCube.pde index 7c7247fc0..2565eb3bc 100644 --- a/examples/Topics/Effects/FireCube/FireCube.pde +++ b/processing/mode/examples/Topics/Effects/FireCube/FireCube.pde @@ -28,6 +28,7 @@ void setup(){ // Create buffered image for 3d cube pg = createGraphics(width, height, P3D); + pg.loadPixels(); calc1 = new int[width]; calc3 = new int[width]; diff --git a/examples/Topics/Effects/Lens/Lens.pde b/processing/mode/examples/Topics/Effects/Lens/Lens.pde similarity index 97% rename from examples/Topics/Effects/Lens/Lens.pde rename to processing/mode/examples/Topics/Effects/Lens/Lens.pde index 804ff14fd..b9b63287b 100644 --- a/examples/Topics/Effects/Lens/Lens.pde +++ b/processing/mode/examples/Topics/Effects/Lens/Lens.pde @@ -28,7 +28,7 @@ int dy = 1; void setup() { - size(640, 360); + size(640, 360, P2D); // Create buffered image for lens effect lensEffect = createGraphics(width, height, P2D); @@ -78,14 +78,13 @@ void draw() { xx += dx; yy += dy; - lensImage = createGraphics(lensD, lensD, P2D); - // save the backgrounlensD of lensHeight*lensWilensDth pixels rectangle at the coorlensDinates // where the lens effect will be applielensD. lensImage2.copy(lensEffect, xx, yy, lensD, lensD, 0, 0, lensD, lensD); // output into a bufferelensD image for reuse lensImage.loadPixels(); + lensImage2.loadPixels(); // For each pixel in the destination rectangle, apply the color // from the appropriate pixel in the saved background. The lensArray diff --git a/examples/Topics/Effects/Lens/data/red_smoke.jpg b/processing/mode/examples/Topics/Effects/Lens/data/red_smoke.jpg similarity index 100% rename from examples/Topics/Effects/Lens/data/red_smoke.jpg rename to processing/mode/examples/Topics/Effects/Lens/data/red_smoke.jpg diff --git a/examples/Topics/Effects/Metaball/Metaball.pde b/processing/mode/examples/Topics/Effects/Metaball/Metaball.pde similarity index 98% rename from examples/Topics/Effects/Metaball/Metaball.pde rename to processing/mode/examples/Topics/Effects/Metaball/Metaball.pde index cde5d1c4a..bf21121f1 100644 --- a/examples/Topics/Effects/Metaball/Metaball.pde +++ b/processing/mode/examples/Topics/Effects/Metaball/Metaball.pde @@ -20,7 +20,7 @@ PGraphics pg; int[][] vy,vx; void setup() { - size(640, 360); + size(640, 360, P2D); pg = createGraphics(160, 90, P2D); vy = new int[numBlobs][pg.height]; vx = new int[numBlobs][pg.width]; diff --git a/examples/Topics/Effects/Plasma/Plasma.pde b/processing/mode/examples/Topics/Effects/Plasma/Plasma.pde similarity index 98% rename from examples/Topics/Effects/Plasma/Plasma.pde rename to processing/mode/examples/Topics/Effects/Plasma/Plasma.pde index 04b20d84d..17162ba8c 100644 --- a/examples/Topics/Effects/Plasma/Plasma.pde +++ b/processing/mode/examples/Topics/Effects/Plasma/Plasma.pde @@ -11,7 +11,7 @@ int pixelSize=2; PGraphics pg; void setup(){ - size(640, 360); + size(640, 360, P2D); // Create buffered image for plasma effect pg = createGraphics(160, 90, P2D); colorMode(HSB); diff --git a/examples/Topics/Effects/Tunnel/Tunnel.pde b/processing/mode/examples/Topics/Effects/Tunnel/Tunnel.pde similarity index 99% rename from examples/Topics/Effects/Tunnel/Tunnel.pde rename to processing/mode/examples/Topics/Effects/Tunnel/Tunnel.pde index 5e98f3e47..c80eda08f 100644 --- a/examples/Topics/Effects/Tunnel/Tunnel.pde +++ b/processing/mode/examples/Topics/Effects/Tunnel/Tunnel.pde @@ -24,7 +24,7 @@ int[][] shadeTable; int w, h; void setup() { - size(640, 360); + size(640, 360, P2D); // Load texture 512 x 512 textureImg = loadImage("red_smoke.jpg"); diff --git a/examples/Topics/Effects/Tunnel/data/red_smoke.jpg b/processing/mode/examples/Topics/Effects/Tunnel/data/red_smoke.jpg similarity index 100% rename from examples/Topics/Effects/Tunnel/data/red_smoke.jpg rename to processing/mode/examples/Topics/Effects/Tunnel/data/red_smoke.jpg diff --git a/examples/Topics/Effects/UnlimitedSprites/UnlimitedSprites.pde b/processing/mode/examples/Topics/Effects/UnlimitedSprites/UnlimitedSprites.pde similarity index 95% rename from examples/Topics/Effects/UnlimitedSprites/UnlimitedSprites.pde rename to processing/mode/examples/Topics/Effects/UnlimitedSprites/UnlimitedSprites.pde index 6395eeaa1..e027e1e99 100644 --- a/examples/Topics/Effects/UnlimitedSprites/UnlimitedSprites.pde +++ b/processing/mode/examples/Topics/Effects/UnlimitedSprites/UnlimitedSprites.pde @@ -27,7 +27,7 @@ void setup() { // Create blank surfaces to draw on for (int i = 0; i < spriteFrames.length; i++) { - spriteFrames[i] = createGraphics(width, height, JAVA2D); + spriteFrames[i] = createGraphics(width, height); } } diff --git a/examples/Topics/Effects/UnlimitedSprites/data/Aqua-Ball-48x48.png b/processing/mode/examples/Topics/Effects/UnlimitedSprites/data/Aqua-Ball-48x48.png similarity index 100% rename from examples/Topics/Effects/UnlimitedSprites/data/Aqua-Ball-48x48.png rename to processing/mode/examples/Topics/Effects/UnlimitedSprites/data/Aqua-Ball-48x48.png diff --git a/examples/Topics/Effects/Wormhole/Wormhole.pde b/processing/mode/examples/Topics/Effects/Wormhole/Wormhole.pde similarity index 100% rename from examples/Topics/Effects/Wormhole/Wormhole.pde rename to processing/mode/examples/Topics/Effects/Wormhole/Wormhole.pde diff --git a/examples/Topics/Effects/Wormhole/data/texture.gif b/processing/mode/examples/Topics/Effects/Wormhole/data/texture.gif similarity index 100% rename from examples/Topics/Effects/Wormhole/data/texture.gif rename to processing/mode/examples/Topics/Effects/Wormhole/data/texture.gif diff --git a/examples/Topics/Effects/Wormhole/data/wormhole.png b/processing/mode/examples/Topics/Effects/Wormhole/data/wormhole.png similarity index 100% rename from examples/Topics/Effects/Wormhole/data/wormhole.png rename to processing/mode/examples/Topics/Effects/Wormhole/data/wormhole.png diff --git a/examples/Topics/File IO/LoadFile1/LoadFile1.pde b/processing/mode/examples/Topics/File IO/LoadFile1/LoadFile1.pde similarity index 100% rename from examples/Topics/File IO/LoadFile1/LoadFile1.pde rename to processing/mode/examples/Topics/File IO/LoadFile1/LoadFile1.pde diff --git a/examples/Topics/File IO/LoadFile1/data/positions.txt b/processing/mode/examples/Topics/File IO/LoadFile1/data/positions.txt similarity index 100% rename from examples/Topics/File IO/LoadFile1/data/positions.txt rename to processing/mode/examples/Topics/File IO/LoadFile1/data/positions.txt diff --git a/examples/Topics/File IO/LoadFile2/LoadFile2.pde b/processing/mode/examples/Topics/File IO/LoadFile2/LoadFile2.pde similarity index 100% rename from examples/Topics/File IO/LoadFile2/LoadFile2.pde rename to processing/mode/examples/Topics/File IO/LoadFile2/LoadFile2.pde diff --git a/examples/Topics/File IO/LoadFile2/Record.pde b/processing/mode/examples/Topics/File IO/LoadFile2/Record.pde similarity index 100% rename from examples/Topics/File IO/LoadFile2/Record.pde rename to processing/mode/examples/Topics/File IO/LoadFile2/Record.pde diff --git a/examples/Topics/File IO/LoadFile2/data/TheSans-Plain-12.vlw b/processing/mode/examples/Topics/File IO/LoadFile2/data/TheSans-Plain-12.vlw similarity index 100% rename from examples/Topics/File IO/LoadFile2/data/TheSans-Plain-12.vlw rename to processing/mode/examples/Topics/File IO/LoadFile2/data/TheSans-Plain-12.vlw diff --git a/examples/Topics/File IO/LoadFile2/data/cars2.tsv b/processing/mode/examples/Topics/File IO/LoadFile2/data/cars2.tsv similarity index 100% rename from examples/Topics/File IO/LoadFile2/data/cars2.tsv rename to processing/mode/examples/Topics/File IO/LoadFile2/data/cars2.tsv diff --git a/examples/Topics/File IO/SaveFile1/SaveFile1.pde b/processing/mode/examples/Topics/File IO/SaveFile1/SaveFile1.pde similarity index 100% rename from examples/Topics/File IO/SaveFile1/SaveFile1.pde rename to processing/mode/examples/Topics/File IO/SaveFile1/SaveFile1.pde diff --git a/examples/Topics/File IO/SaveFile2/SaveFile2.pde b/processing/mode/examples/Topics/File IO/SaveFile2/SaveFile2.pde similarity index 100% rename from examples/Topics/File IO/SaveFile2/SaveFile2.pde rename to processing/mode/examples/Topics/File IO/SaveFile2/SaveFile2.pde diff --git a/examples/Topics/File IO/SaveManyImages/SaveManyImages.pde b/processing/mode/examples/Topics/File IO/SaveManyImages/SaveManyImages.pde similarity index 100% rename from examples/Topics/File IO/SaveManyImages/SaveManyImages.pde rename to processing/mode/examples/Topics/File IO/SaveManyImages/SaveManyImages.pde diff --git a/examples/Topics/File IO/SaveOneImage/SaveOneImage.pde b/processing/mode/examples/Topics/File IO/SaveOneImage/SaveOneImage.pde similarity index 100% rename from examples/Topics/File IO/SaveOneImage/SaveOneImage.pde rename to processing/mode/examples/Topics/File IO/SaveOneImage/SaveOneImage.pde diff --git a/examples/Topics/File IO/TileImages/TileImages.pde b/processing/mode/examples/Topics/File IO/TileImages/TileImages.pde similarity index 100% rename from examples/Topics/File IO/TileImages/TileImages.pde rename to processing/mode/examples/Topics/File IO/TileImages/TileImages.pde diff --git a/examples/Topics/Fractals and L-Systems/Koch/Koch.pde b/processing/mode/examples/Topics/Fractals and L-Systems/Koch/Koch.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/Koch/Koch.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/Koch/Koch.pde diff --git a/examples/Topics/Fractals and L-Systems/Mandelbrot/Mandelbrot.pde b/processing/mode/examples/Topics/Fractals and L-Systems/Mandelbrot/Mandelbrot.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/Mandelbrot/Mandelbrot.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/Mandelbrot/Mandelbrot.pde diff --git a/examples/Topics/Fractals and L-Systems/PenroseSnowflake/LSystem.pde b/processing/mode/examples/Topics/Fractals and L-Systems/PenroseSnowflake/LSystem.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/PenroseSnowflake/LSystem.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/PenroseSnowflake/LSystem.pde diff --git a/examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflake.pde b/processing/mode/examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflake.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflake.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflake.pde diff --git a/examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflakeLSystem.pde b/processing/mode/examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflakeLSystem.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflakeLSystem.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/PenroseSnowflake/PenroseSnowflakeLSystem.pde diff --git a/examples/Topics/Fractals and L-Systems/PenroseTile/LSystem.pde b/processing/mode/examples/Topics/Fractals and L-Systems/PenroseTile/LSystem.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/PenroseTile/LSystem.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/PenroseTile/LSystem.pde diff --git a/examples/Topics/Fractals and L-Systems/PenroseTile/PenroseLSystem.pde b/processing/mode/examples/Topics/Fractals and L-Systems/PenroseTile/PenroseLSystem.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/PenroseTile/PenroseLSystem.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/PenroseTile/PenroseLSystem.pde diff --git a/examples/Topics/Fractals and L-Systems/PenroseTile/PenroseTile.pde b/processing/mode/examples/Topics/Fractals and L-Systems/PenroseTile/PenroseTile.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/PenroseTile/PenroseTile.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/PenroseTile/PenroseTile.pde diff --git a/examples/Topics/Fractals and L-Systems/Pentigree/LSystem.pde b/processing/mode/examples/Topics/Fractals and L-Systems/Pentigree/LSystem.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/Pentigree/LSystem.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/Pentigree/LSystem.pde diff --git a/examples/Topics/Fractals and L-Systems/Pentigree/Pentigree.pde b/processing/mode/examples/Topics/Fractals and L-Systems/Pentigree/Pentigree.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/Pentigree/Pentigree.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/Pentigree/Pentigree.pde diff --git a/examples/Topics/Fractals and L-Systems/Pentigree/PentigreeLSystem.pde b/processing/mode/examples/Topics/Fractals and L-Systems/Pentigree/PentigreeLSystem.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/Pentigree/PentigreeLSystem.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/Pentigree/PentigreeLSystem.pde diff --git a/examples/Topics/Fractals and L-Systems/Tree/Tree.pde b/processing/mode/examples/Topics/Fractals and L-Systems/Tree/Tree.pde similarity index 100% rename from examples/Topics/Fractals and L-Systems/Tree/Tree.pde rename to processing/mode/examples/Topics/Fractals and L-Systems/Tree/Tree.pde diff --git a/examples/Topics/GUI/Button/Button.pde b/processing/mode/examples/Topics/GUI/Button/Button.pde similarity index 100% rename from examples/Topics/GUI/Button/Button.pde rename to processing/mode/examples/Topics/GUI/Button/Button.pde diff --git a/examples/Topics/GUI/Buttons/Buttons.pde b/processing/mode/examples/Topics/GUI/Buttons/Buttons.pde similarity index 100% rename from examples/Topics/GUI/Buttons/Buttons.pde rename to processing/mode/examples/Topics/GUI/Buttons/Buttons.pde diff --git a/examples/Topics/GUI/Handles/Handles.pde b/processing/mode/examples/Topics/GUI/Handles/Handles.pde similarity index 100% rename from examples/Topics/GUI/Handles/Handles.pde rename to processing/mode/examples/Topics/GUI/Handles/Handles.pde diff --git a/examples/Topics/GUI/ImageButton/ImageButton.pde b/processing/mode/examples/Topics/GUI/ImageButton/ImageButton.pde similarity index 100% rename from examples/Topics/GUI/ImageButton/ImageButton.pde rename to processing/mode/examples/Topics/GUI/ImageButton/ImageButton.pde diff --git a/examples/Topics/GUI/ImageButton/data/base.gif b/processing/mode/examples/Topics/GUI/ImageButton/data/base.gif similarity index 100% rename from examples/Topics/GUI/ImageButton/data/base.gif rename to processing/mode/examples/Topics/GUI/ImageButton/data/base.gif diff --git a/examples/Topics/GUI/ImageButton/data/down.gif b/processing/mode/examples/Topics/GUI/ImageButton/data/down.gif similarity index 100% rename from examples/Topics/GUI/ImageButton/data/down.gif rename to processing/mode/examples/Topics/GUI/ImageButton/data/down.gif diff --git a/examples/Topics/GUI/ImageButton/data/roll.gif b/processing/mode/examples/Topics/GUI/ImageButton/data/roll.gif similarity index 100% rename from examples/Topics/GUI/ImageButton/data/roll.gif rename to processing/mode/examples/Topics/GUI/ImageButton/data/roll.gif diff --git a/examples/Topics/GUI/Rollover/Rollover.pde b/processing/mode/examples/Topics/GUI/Rollover/Rollover.pde similarity index 100% rename from examples/Topics/GUI/Rollover/Rollover.pde rename to processing/mode/examples/Topics/GUI/Rollover/Rollover.pde diff --git a/examples/Topics/GUI/Scrollbar/Scrollbar.pde b/processing/mode/examples/Topics/GUI/Scrollbar/Scrollbar.pde similarity index 100% rename from examples/Topics/GUI/Scrollbar/Scrollbar.pde rename to processing/mode/examples/Topics/GUI/Scrollbar/Scrollbar.pde diff --git a/examples/Topics/GUI/Scrollbar/data/seedBottom.jpg b/processing/mode/examples/Topics/GUI/Scrollbar/data/seedBottom.jpg similarity index 100% rename from examples/Topics/GUI/Scrollbar/data/seedBottom.jpg rename to processing/mode/examples/Topics/GUI/Scrollbar/data/seedBottom.jpg diff --git a/examples/Topics/GUI/Scrollbar/data/seedTop.jpg b/processing/mode/examples/Topics/GUI/Scrollbar/data/seedTop.jpg similarity index 100% rename from examples/Topics/GUI/Scrollbar/data/seedTop.jpg rename to processing/mode/examples/Topics/GUI/Scrollbar/data/seedTop.jpg diff --git a/examples/Topics/Geometry/Icosahedra/Dimension3D.pde b/processing/mode/examples/Topics/Geometry/Icosahedra/Dimension3D.pde similarity index 100% rename from examples/Topics/Geometry/Icosahedra/Dimension3D.pde rename to processing/mode/examples/Topics/Geometry/Icosahedra/Dimension3D.pde diff --git a/examples/Topics/Geometry/Icosahedra/Icosahedra.pde b/processing/mode/examples/Topics/Geometry/Icosahedra/Icosahedra.pde similarity index 100% rename from examples/Topics/Geometry/Icosahedra/Icosahedra.pde rename to processing/mode/examples/Topics/Geometry/Icosahedra/Icosahedra.pde diff --git a/examples/Topics/Geometry/Icosahedra/Icosahedron.pde b/processing/mode/examples/Topics/Geometry/Icosahedra/Icosahedron.pde similarity index 100% rename from examples/Topics/Geometry/Icosahedra/Icosahedron.pde rename to processing/mode/examples/Topics/Geometry/Icosahedra/Icosahedron.pde diff --git a/examples/Topics/Geometry/Icosahedra/Shape3D.pde b/processing/mode/examples/Topics/Geometry/Icosahedra/Shape3D.pde similarity index 100% rename from examples/Topics/Geometry/Icosahedra/Shape3D.pde rename to processing/mode/examples/Topics/Geometry/Icosahedra/Shape3D.pde diff --git a/examples/Topics/Geometry/NoiseSphere/NoiseSphere.pde b/processing/mode/examples/Topics/Geometry/NoiseSphere/NoiseSphere.pde similarity index 100% rename from examples/Topics/Geometry/NoiseSphere/NoiseSphere.pde rename to processing/mode/examples/Topics/Geometry/NoiseSphere/NoiseSphere.pde diff --git a/examples/Topics/Geometry/RGBCube/RGBCube.pde b/processing/mode/examples/Topics/Geometry/RGBCube/RGBCube.pde similarity index 100% rename from examples/Topics/Geometry/RGBCube/RGBCube.pde rename to processing/mode/examples/Topics/Geometry/RGBCube/RGBCube.pde diff --git a/examples/Topics/Geometry/ShapeTransform/ShapeTransform.pde b/processing/mode/examples/Topics/Geometry/ShapeTransform/ShapeTransform.pde similarity index 100% rename from examples/Topics/Geometry/ShapeTransform/ShapeTransform.pde rename to processing/mode/examples/Topics/Geometry/ShapeTransform/ShapeTransform.pde diff --git a/examples/Topics/Geometry/SpaceJunk/Cube.pde b/processing/mode/examples/Topics/Geometry/SpaceJunk/Cube.pde similarity index 100% rename from examples/Topics/Geometry/SpaceJunk/Cube.pde rename to processing/mode/examples/Topics/Geometry/SpaceJunk/Cube.pde diff --git a/examples/Topics/Geometry/SpaceJunk/SpaceJunk.pde b/processing/mode/examples/Topics/Geometry/SpaceJunk/SpaceJunk.pde similarity index 100% rename from examples/Topics/Geometry/SpaceJunk/SpaceJunk.pde rename to processing/mode/examples/Topics/Geometry/SpaceJunk/SpaceJunk.pde diff --git a/examples/Topics/Geometry/Toroid/Toroid.pde b/processing/mode/examples/Topics/Geometry/Toroid/Toroid.pde similarity index 100% rename from examples/Topics/Geometry/Toroid/Toroid.pde rename to processing/mode/examples/Topics/Geometry/Toroid/Toroid.pde diff --git a/examples/Topics/Geometry/Vertices/Vertices.pde b/processing/mode/examples/Topics/Geometry/Vertices/Vertices.pde similarity index 100% rename from examples/Topics/Geometry/Vertices/Vertices.pde rename to processing/mode/examples/Topics/Geometry/Vertices/Vertices.pde diff --git a/examples/Topics/Image Processing/Blur/Blur.pde b/processing/mode/examples/Topics/Image Processing/Blur/Blur.pde similarity index 98% rename from examples/Topics/Image Processing/Blur/Blur.pde rename to processing/mode/examples/Topics/Image Processing/Blur/Blur.pde index 67e9ef586..3eb7103d2 100644 --- a/examples/Topics/Image Processing/Blur/Blur.pde +++ b/processing/mode/examples/Topics/Image Processing/Blur/Blur.pde @@ -18,6 +18,7 @@ img.loadPixels(); // Create an opaque image of the same size as the original PImage edgeImg = createImage(img.width, img.height, RGB); +edgeImg.loadPixels(); // Loop through every pixel in the image. for (int y = 1; y < img.height-1; y++) { // Skip top and bottom edges diff --git a/examples/Topics/Image Processing/Blur/data/trees.jpg b/processing/mode/examples/Topics/Image Processing/Blur/data/trees.jpg similarity index 100% rename from examples/Topics/Image Processing/Blur/data/trees.jpg rename to processing/mode/examples/Topics/Image Processing/Blur/data/trees.jpg diff --git a/examples/Topics/Image Processing/Brightness/Brightness.pde b/processing/mode/examples/Topics/Image Processing/Brightness/Brightness.pde similarity index 100% rename from examples/Topics/Image Processing/Brightness/Brightness.pde rename to processing/mode/examples/Topics/Image Processing/Brightness/Brightness.pde diff --git a/examples/Topics/Image Processing/Brightness/data/wires.jpg b/processing/mode/examples/Topics/Image Processing/Brightness/data/wires.jpg similarity index 100% rename from examples/Topics/Image Processing/Brightness/data/wires.jpg rename to processing/mode/examples/Topics/Image Processing/Brightness/data/wires.jpg diff --git a/examples/Topics/Image Processing/Convolution/Convolution.pde b/processing/mode/examples/Topics/Image Processing/Convolution/Convolution.pde similarity index 99% rename from examples/Topics/Image Processing/Convolution/Convolution.pde rename to processing/mode/examples/Topics/Image Processing/Convolution/Convolution.pde index bb3f7eda5..5640c89b1 100644 --- a/examples/Topics/Image Processing/Convolution/Convolution.pde +++ b/processing/mode/examples/Topics/Image Processing/Convolution/Convolution.pde @@ -20,6 +20,7 @@ void setup() { size(200, 200); frameRate(30); img = loadImage("end.jpg"); + img.loadPixels(); } void draw() { diff --git a/examples/Topics/Image Processing/Convolution/data/end.jpg b/processing/mode/examples/Topics/Image Processing/Convolution/data/end.jpg similarity index 100% rename from examples/Topics/Image Processing/Convolution/data/end.jpg rename to processing/mode/examples/Topics/Image Processing/Convolution/data/end.jpg diff --git a/examples/Topics/Image Processing/Convolution/data/sunflower.jpg b/processing/mode/examples/Topics/Image Processing/Convolution/data/sunflower.jpg similarity index 100% rename from examples/Topics/Image Processing/Convolution/data/sunflower.jpg rename to processing/mode/examples/Topics/Image Processing/Convolution/data/sunflower.jpg diff --git a/examples/Topics/Image Processing/EdgeDetection/EdgeDetection.pde b/processing/mode/examples/Topics/Image Processing/EdgeDetection/EdgeDetection.pde similarity index 100% rename from examples/Topics/Image Processing/EdgeDetection/EdgeDetection.pde rename to processing/mode/examples/Topics/Image Processing/EdgeDetection/EdgeDetection.pde diff --git a/examples/Topics/Image Processing/EdgeDetection/data/house.jpg b/processing/mode/examples/Topics/Image Processing/EdgeDetection/data/house.jpg similarity index 100% rename from examples/Topics/Image Processing/EdgeDetection/data/house.jpg rename to processing/mode/examples/Topics/Image Processing/EdgeDetection/data/house.jpg diff --git a/examples/Topics/Image Processing/Histogram/Histogram.pde b/processing/mode/examples/Topics/Image Processing/Histogram/Histogram.pde similarity index 100% rename from examples/Topics/Image Processing/Histogram/Histogram.pde rename to processing/mode/examples/Topics/Image Processing/Histogram/Histogram.pde diff --git a/examples/Topics/Image Processing/Histogram/data/cdi01_g.jpg b/processing/mode/examples/Topics/Image Processing/Histogram/data/cdi01_g.jpg similarity index 100% rename from examples/Topics/Image Processing/Histogram/data/cdi01_g.jpg rename to processing/mode/examples/Topics/Image Processing/Histogram/data/cdi01_g.jpg diff --git a/examples/Topics/Image Processing/Histogram/data/ystone08.jpg b/processing/mode/examples/Topics/Image Processing/Histogram/data/ystone08.jpg similarity index 100% rename from examples/Topics/Image Processing/Histogram/data/ystone08.jpg rename to processing/mode/examples/Topics/Image Processing/Histogram/data/ystone08.jpg diff --git a/examples/Topics/Image Processing/LinearImage/LinearImage.pde b/processing/mode/examples/Topics/Image Processing/LinearImage/LinearImage.pde similarity index 100% rename from examples/Topics/Image Processing/LinearImage/LinearImage.pde rename to processing/mode/examples/Topics/Image Processing/LinearImage/LinearImage.pde diff --git a/examples/Topics/Image Processing/LinearImage/data/florence03.jpg b/processing/mode/examples/Topics/Image Processing/LinearImage/data/florence03.jpg similarity index 100% rename from examples/Topics/Image Processing/LinearImage/data/florence03.jpg rename to processing/mode/examples/Topics/Image Processing/LinearImage/data/florence03.jpg diff --git a/examples/Topics/Image Processing/PixelArray/PixelArray.pde b/processing/mode/examples/Topics/Image Processing/PixelArray/PixelArray.pde similarity index 100% rename from examples/Topics/Image Processing/PixelArray/PixelArray.pde rename to processing/mode/examples/Topics/Image Processing/PixelArray/PixelArray.pde diff --git a/examples/Topics/Image Processing/PixelArray/data/ystone08.jpg b/processing/mode/examples/Topics/Image Processing/PixelArray/data/ystone08.jpg similarity index 100% rename from examples/Topics/Image Processing/PixelArray/data/ystone08.jpg rename to processing/mode/examples/Topics/Image Processing/PixelArray/data/ystone08.jpg diff --git a/examples/Topics/Interaction/Follow1/Follow1.pde b/processing/mode/examples/Topics/Interaction/Follow1/Follow1.pde similarity index 100% rename from examples/Topics/Interaction/Follow1/Follow1.pde rename to processing/mode/examples/Topics/Interaction/Follow1/Follow1.pde diff --git a/examples/Topics/Interaction/Follow2/Follow2.pde b/processing/mode/examples/Topics/Interaction/Follow2/Follow2.pde similarity index 100% rename from examples/Topics/Interaction/Follow2/Follow2.pde rename to processing/mode/examples/Topics/Interaction/Follow2/Follow2.pde diff --git a/examples/Topics/Interaction/Follow3/Follow3.pde b/processing/mode/examples/Topics/Interaction/Follow3/Follow3.pde similarity index 100% rename from examples/Topics/Interaction/Follow3/Follow3.pde rename to processing/mode/examples/Topics/Interaction/Follow3/Follow3.pde diff --git a/processing/mode/examples/Topics/Interaction/Multitouch/Multitouch.pde b/processing/mode/examples/Topics/Interaction/Multitouch/Multitouch.pde new file mode 100644 index 000000000..dd0b19dba --- /dev/null +++ b/processing/mode/examples/Topics/Interaction/Multitouch/Multitouch.pde @@ -0,0 +1,33 @@ +float scale; + +void setup() { + fullScreen(); + noStroke(); + scale = displayDensity / 2.65; + colorMode(HSB, 350, 100, 100); + textFont(createFont("SansSerif", scale * 60)); +} + +void draw() { + background(30, 0, 100); + fill(30, 0, 20); + text("Number of touch points: " + touches.length, 20, scale * 100); + for (int i = 0; i < touches.length; i++) { + float s = scale * map(touches[i].area, 0, 1, 50, 500); + println(touches[i].area); + fill(30, map(touches[i].pressure, 0.6, 1.6, 0, 100), 70, 200); + ellipse(touches[i].x, touches[i].y, s, s); + } +} + +void touchStarted() { + println("Touch started"); +} + +void touchEnded() { + println("Touch ended"); +} + +void touchMoved() { + println("Touch moved"); +} \ No newline at end of file diff --git a/examples/Topics/Interaction/Reach1/Reach1.pde b/processing/mode/examples/Topics/Interaction/Reach1/Reach1.pde similarity index 100% rename from examples/Topics/Interaction/Reach1/Reach1.pde rename to processing/mode/examples/Topics/Interaction/Reach1/Reach1.pde diff --git a/examples/Topics/Interaction/Reach2/Reach2.pde b/processing/mode/examples/Topics/Interaction/Reach2/Reach2.pde similarity index 100% rename from examples/Topics/Interaction/Reach2/Reach2.pde rename to processing/mode/examples/Topics/Interaction/Reach2/Reach2.pde diff --git a/examples/Topics/Interaction/Reach3/Reach3.pde b/processing/mode/examples/Topics/Interaction/Reach3/Reach3.pde similarity index 100% rename from examples/Topics/Interaction/Reach3/Reach3.pde rename to processing/mode/examples/Topics/Interaction/Reach3/Reach3.pde diff --git a/examples/Topics/Interaction/Tickle/Tickle.pde b/processing/mode/examples/Topics/Interaction/Tickle/Tickle.pde similarity index 100% rename from examples/Topics/Interaction/Tickle/Tickle.pde rename to processing/mode/examples/Topics/Interaction/Tickle/Tickle.pde diff --git a/examples/Topics/Interaction/Tickle/data/AmericanTypewriter-24.vlw b/processing/mode/examples/Topics/Interaction/Tickle/data/AmericanTypewriter-24.vlw similarity index 100% rename from examples/Topics/Interaction/Tickle/data/AmericanTypewriter-24.vlw rename to processing/mode/examples/Topics/Interaction/Tickle/data/AmericanTypewriter-24.vlw diff --git a/examples/Topics/Motion/Bounce/Bounce.pde b/processing/mode/examples/Topics/Motion/Bounce/Bounce.pde similarity index 100% rename from examples/Topics/Motion/Bounce/Bounce.pde rename to processing/mode/examples/Topics/Motion/Bounce/Bounce.pde diff --git a/examples/Topics/Motion/BouncyBubbles/BouncyBubbles.pde b/processing/mode/examples/Topics/Motion/BouncyBubbles/BouncyBubbles.pde similarity index 100% rename from examples/Topics/Motion/BouncyBubbles/BouncyBubbles.pde rename to processing/mode/examples/Topics/Motion/BouncyBubbles/BouncyBubbles.pde diff --git a/examples/Topics/Motion/Brownian/Brownian.pde b/processing/mode/examples/Topics/Motion/Brownian/Brownian.pde similarity index 100% rename from examples/Topics/Motion/Brownian/Brownian.pde rename to processing/mode/examples/Topics/Motion/Brownian/Brownian.pde diff --git a/examples/Topics/Motion/CircleCollision/Ball.pde b/processing/mode/examples/Topics/Motion/CircleCollision/Ball.pde similarity index 100% rename from examples/Topics/Motion/CircleCollision/Ball.pde rename to processing/mode/examples/Topics/Motion/CircleCollision/Ball.pde diff --git a/examples/Topics/Motion/CircleCollision/CircleCollision.pde b/processing/mode/examples/Topics/Motion/CircleCollision/CircleCollision.pde similarity index 100% rename from examples/Topics/Motion/CircleCollision/CircleCollision.pde rename to processing/mode/examples/Topics/Motion/CircleCollision/CircleCollision.pde diff --git a/examples/Topics/Motion/Collision/Collision.pde b/processing/mode/examples/Topics/Motion/Collision/Collision.pde similarity index 100% rename from examples/Topics/Motion/Collision/Collision.pde rename to processing/mode/examples/Topics/Motion/Collision/Collision.pde diff --git a/examples/Topics/Motion/Linear/Linear.pde b/processing/mode/examples/Topics/Motion/Linear/Linear.pde similarity index 100% rename from examples/Topics/Motion/Linear/Linear.pde rename to processing/mode/examples/Topics/Motion/Linear/Linear.pde diff --git a/examples/Topics/Motion/MovingOnCurves/MovingOnCurves.pde b/processing/mode/examples/Topics/Motion/MovingOnCurves/MovingOnCurves.pde similarity index 100% rename from examples/Topics/Motion/MovingOnCurves/MovingOnCurves.pde rename to processing/mode/examples/Topics/Motion/MovingOnCurves/MovingOnCurves.pde diff --git a/examples/Topics/Motion/Puff/Puff.pde b/processing/mode/examples/Topics/Motion/Puff/Puff.pde similarity index 100% rename from examples/Topics/Motion/Puff/Puff.pde rename to processing/mode/examples/Topics/Motion/Puff/Puff.pde diff --git a/examples/Topics/Motion/Reflection1/Reflection1.pde b/processing/mode/examples/Topics/Motion/Reflection1/Reflection1.pde similarity index 100% rename from examples/Topics/Motion/Reflection1/Reflection1.pde rename to processing/mode/examples/Topics/Motion/Reflection1/Reflection1.pde diff --git a/examples/Topics/Motion/Reflection2/Ground.pde b/processing/mode/examples/Topics/Motion/Reflection2/Ground.pde similarity index 100% rename from examples/Topics/Motion/Reflection2/Ground.pde rename to processing/mode/examples/Topics/Motion/Reflection2/Ground.pde diff --git a/examples/Topics/Motion/Reflection2/Orb.pde b/processing/mode/examples/Topics/Motion/Reflection2/Orb.pde similarity index 100% rename from examples/Topics/Motion/Reflection2/Orb.pde rename to processing/mode/examples/Topics/Motion/Reflection2/Orb.pde diff --git a/examples/Topics/Motion/Reflection2/Reflection2.pde b/processing/mode/examples/Topics/Motion/Reflection2/Reflection2.pde similarity index 100% rename from examples/Topics/Motion/Reflection2/Reflection2.pde rename to processing/mode/examples/Topics/Motion/Reflection2/Reflection2.pde diff --git a/examples/Topics/Shaders/BlurFilter/BlurFilter.pde b/processing/mode/examples/Topics/Shaders/BlurFilter/BlurFilter.pde similarity index 100% rename from examples/Topics/Shaders/BlurFilter/BlurFilter.pde rename to processing/mode/examples/Topics/Shaders/BlurFilter/BlurFilter.pde diff --git a/processing/mode/examples/Topics/Shaders/BlurFilter/data/blur.glsl b/processing/mode/examples/Topics/Shaders/BlurFilter/data/blur.glsl new file mode 100644 index 000000000..2aa9d6079 --- /dev/null +++ b/processing/mode/examples/Topics/Shaders/BlurFilter/data/blur.glsl @@ -0,0 +1,42 @@ +#ifdef GL_ES +precision mediump float; +precision mediump int; +#endif + +#define PROCESSING_TEXTURE_SHADER + +uniform sampler2D texture; +uniform vec2 texOffset; + +varying vec4 vertColor; +varying vec4 vertTexCoord; + +void main(void) { + // Grouping texcoord variables in order to make it work in the GMA 950. See post #13 + // in this thread: + // http://www.idevgames.com/forums/thread-3467.html + vec2 tc0 = vertTexCoord.st + vec2(-texOffset.s, -texOffset.t); + vec2 tc1 = vertTexCoord.st + vec2( 0.0, -texOffset.t); + vec2 tc2 = vertTexCoord.st + vec2(+texOffset.s, -texOffset.t); + vec2 tc3 = vertTexCoord.st + vec2(-texOffset.s, 0.0); + vec2 tc4 = vertTexCoord.st + vec2( 0.0, 0.0); + vec2 tc5 = vertTexCoord.st + vec2(+texOffset.s, 0.0); + vec2 tc6 = vertTexCoord.st + vec2(-texOffset.s, +texOffset.t); + vec2 tc7 = vertTexCoord.st + vec2( 0.0, +texOffset.t); + vec2 tc8 = vertTexCoord.st + vec2(+texOffset.s, +texOffset.t); + + vec4 col0 = texture2D(texture, tc0); + vec4 col1 = texture2D(texture, tc1); + vec4 col2 = texture2D(texture, tc2); + vec4 col3 = texture2D(texture, tc3); + vec4 col4 = texture2D(texture, tc4); + vec4 col5 = texture2D(texture, tc5); + vec4 col6 = texture2D(texture, tc6); + vec4 col7 = texture2D(texture, tc7); + vec4 col8 = texture2D(texture, tc8); + + vec4 sum = (1.0 * col0 + 2.0 * col1 + 1.0 * col2 + + 2.0 * col3 + 4.0 * col4 + 2.0 * col4 + + 1.0 * col5 + 2.0 * col6 + 1.0 * col7) / 16.0; + gl_FragColor = vec4(sum.rgb, 1.0) * vertColor; +} diff --git a/examples/Topics/Shaders/EdgeDetect/EdgeDetect.pde b/processing/mode/examples/Topics/Shaders/EdgeDetect/EdgeDetect.pde similarity index 100% rename from examples/Topics/Shaders/EdgeDetect/EdgeDetect.pde rename to processing/mode/examples/Topics/Shaders/EdgeDetect/EdgeDetect.pde diff --git a/examples/Topics/Shaders/EdgeDetect/data/edges.glsl b/processing/mode/examples/Topics/Shaders/EdgeDetect/data/edges.glsl similarity index 100% rename from examples/Topics/Shaders/EdgeDetect/data/edges.glsl rename to processing/mode/examples/Topics/Shaders/EdgeDetect/data/edges.glsl diff --git a/processing/mode/examples/Topics/Shaders/EdgeDetect/data/leaves.jpg b/processing/mode/examples/Topics/Shaders/EdgeDetect/data/leaves.jpg new file mode 100644 index 000000000..72c86a092 Binary files /dev/null and b/processing/mode/examples/Topics/Shaders/EdgeDetect/data/leaves.jpg differ diff --git a/examples/Topics/Shaders/EdgeFilter/EdgeFilter.pde b/processing/mode/examples/Topics/Shaders/EdgeFilter/EdgeFilter.pde similarity index 100% rename from examples/Topics/Shaders/EdgeFilter/EdgeFilter.pde rename to processing/mode/examples/Topics/Shaders/EdgeFilter/EdgeFilter.pde diff --git a/examples/Topics/Shaders/EdgeFilter/data/edges.glsl b/processing/mode/examples/Topics/Shaders/EdgeFilter/data/edges.glsl similarity index 100% rename from examples/Topics/Shaders/EdgeFilter/data/edges.glsl rename to processing/mode/examples/Topics/Shaders/EdgeFilter/data/edges.glsl diff --git a/examples/Topics/Shaders/LowLevelGL/LowLevelGL.pde b/processing/mode/examples/Topics/Shaders/LowLevelGL/LowLevelGL.pde similarity index 100% rename from examples/Topics/Shaders/LowLevelGL/LowLevelGL.pde rename to processing/mode/examples/Topics/Shaders/LowLevelGL/LowLevelGL.pde diff --git a/examples/Topics/Shaders/LowLevelGL/data/frag.glsl b/processing/mode/examples/Topics/Shaders/LowLevelGL/data/frag.glsl similarity index 100% rename from examples/Topics/Shaders/LowLevelGL/data/frag.glsl rename to processing/mode/examples/Topics/Shaders/LowLevelGL/data/frag.glsl diff --git a/examples/Topics/Shaders/LowLevelGL/data/vert.glsl b/processing/mode/examples/Topics/Shaders/LowLevelGL/data/vert.glsl similarity index 100% rename from examples/Topics/Shaders/LowLevelGL/data/vert.glsl rename to processing/mode/examples/Topics/Shaders/LowLevelGL/data/vert.glsl diff --git a/examples/Topics/Shaders/ToonShading/ToonShading.pde b/processing/mode/examples/Topics/Shaders/ToonShading/ToonShading.pde similarity index 100% rename from examples/Topics/Shaders/ToonShading/ToonShading.pde rename to processing/mode/examples/Topics/Shaders/ToonShading/ToonShading.pde diff --git a/examples/Topics/Shaders/ToonShading/data/ToonFrag.glsl b/processing/mode/examples/Topics/Shaders/ToonShading/data/ToonFrag.glsl similarity index 100% rename from examples/Topics/Shaders/ToonShading/data/ToonFrag.glsl rename to processing/mode/examples/Topics/Shaders/ToonShading/data/ToonFrag.glsl diff --git a/examples/Topics/Shaders/ToonShading/data/ToonVert.glsl b/processing/mode/examples/Topics/Shaders/ToonShading/data/ToonVert.glsl similarity index 100% rename from examples/Topics/Shaders/ToonShading/data/ToonVert.glsl rename to processing/mode/examples/Topics/Shaders/ToonShading/data/ToonVert.glsl diff --git a/examples/Topics/Simulate/Chain/Chain.pde b/processing/mode/examples/Topics/Simulate/Chain/Chain.pde similarity index 100% rename from examples/Topics/Simulate/Chain/Chain.pde rename to processing/mode/examples/Topics/Simulate/Chain/Chain.pde diff --git a/examples/Topics/Simulate/Flocking/Boid.pde b/processing/mode/examples/Topics/Simulate/Flocking/Boid.pde similarity index 100% rename from examples/Topics/Simulate/Flocking/Boid.pde rename to processing/mode/examples/Topics/Simulate/Flocking/Boid.pde diff --git a/examples/Topics/Simulate/Flocking/Flock.pde b/processing/mode/examples/Topics/Simulate/Flocking/Flock.pde similarity index 100% rename from examples/Topics/Simulate/Flocking/Flock.pde rename to processing/mode/examples/Topics/Simulate/Flocking/Flock.pde diff --git a/examples/Topics/Simulate/Flocking/Flocking.pde b/processing/mode/examples/Topics/Simulate/Flocking/Flocking.pde similarity index 100% rename from examples/Topics/Simulate/Flocking/Flocking.pde rename to processing/mode/examples/Topics/Simulate/Flocking/Flocking.pde diff --git a/examples/Topics/Simulate/ForcesWithVectors/ForcesWithVectors.pde b/processing/mode/examples/Topics/Simulate/ForcesWithVectors/ForcesWithVectors.pde similarity index 100% rename from examples/Topics/Simulate/ForcesWithVectors/ForcesWithVectors.pde rename to processing/mode/examples/Topics/Simulate/ForcesWithVectors/ForcesWithVectors.pde diff --git a/examples/Topics/Simulate/ForcesWithVectors/Liquid.pde b/processing/mode/examples/Topics/Simulate/ForcesWithVectors/Liquid.pde similarity index 100% rename from examples/Topics/Simulate/ForcesWithVectors/Liquid.pde rename to processing/mode/examples/Topics/Simulate/ForcesWithVectors/Liquid.pde diff --git a/examples/Topics/Simulate/ForcesWithVectors/Mover.pde b/processing/mode/examples/Topics/Simulate/ForcesWithVectors/Mover.pde similarity index 100% rename from examples/Topics/Simulate/ForcesWithVectors/Mover.pde rename to processing/mode/examples/Topics/Simulate/ForcesWithVectors/Mover.pde diff --git a/examples/Topics/Simulate/GravitationalAttraction3D/GravitationalAttraction3D.pde b/processing/mode/examples/Topics/Simulate/GravitationalAttraction3D/GravitationalAttraction3D.pde similarity index 100% rename from examples/Topics/Simulate/GravitationalAttraction3D/GravitationalAttraction3D.pde rename to processing/mode/examples/Topics/Simulate/GravitationalAttraction3D/GravitationalAttraction3D.pde diff --git a/examples/Topics/Simulate/GravitationalAttraction3D/Planet.pde b/processing/mode/examples/Topics/Simulate/GravitationalAttraction3D/Planet.pde similarity index 100% rename from examples/Topics/Simulate/GravitationalAttraction3D/Planet.pde rename to processing/mode/examples/Topics/Simulate/GravitationalAttraction3D/Planet.pde diff --git a/examples/Topics/Simulate/GravitationalAttraction3D/Sun.pde b/processing/mode/examples/Topics/Simulate/GravitationalAttraction3D/Sun.pde similarity index 100% rename from examples/Topics/Simulate/GravitationalAttraction3D/Sun.pde rename to processing/mode/examples/Topics/Simulate/GravitationalAttraction3D/Sun.pde diff --git a/examples/Topics/Simulate/MultipleParticleSystems/CrazyParticle.pde b/processing/mode/examples/Topics/Simulate/MultipleParticleSystems/CrazyParticle.pde similarity index 100% rename from examples/Topics/Simulate/MultipleParticleSystems/CrazyParticle.pde rename to processing/mode/examples/Topics/Simulate/MultipleParticleSystems/CrazyParticle.pde diff --git a/examples/Topics/Simulate/MultipleParticleSystems/MultipleParticleSystems.pde b/processing/mode/examples/Topics/Simulate/MultipleParticleSystems/MultipleParticleSystems.pde similarity index 100% rename from examples/Topics/Simulate/MultipleParticleSystems/MultipleParticleSystems.pde rename to processing/mode/examples/Topics/Simulate/MultipleParticleSystems/MultipleParticleSystems.pde diff --git a/examples/Topics/Simulate/MultipleParticleSystems/Particle.pde b/processing/mode/examples/Topics/Simulate/MultipleParticleSystems/Particle.pde similarity index 100% rename from examples/Topics/Simulate/MultipleParticleSystems/Particle.pde rename to processing/mode/examples/Topics/Simulate/MultipleParticleSystems/Particle.pde diff --git a/examples/Topics/Simulate/MultipleParticleSystems/ParticleSystem.pde b/processing/mode/examples/Topics/Simulate/MultipleParticleSystems/ParticleSystem.pde similarity index 100% rename from examples/Topics/Simulate/MultipleParticleSystems/ParticleSystem.pde rename to processing/mode/examples/Topics/Simulate/MultipleParticleSystems/ParticleSystem.pde diff --git a/examples/Topics/Simulate/SimpleParticleSystem/Particle.pde b/processing/mode/examples/Topics/Simulate/SimpleParticleSystem/Particle.pde similarity index 100% rename from examples/Topics/Simulate/SimpleParticleSystem/Particle.pde rename to processing/mode/examples/Topics/Simulate/SimpleParticleSystem/Particle.pde diff --git a/examples/Topics/Simulate/SimpleParticleSystem/ParticleSystem.pde b/processing/mode/examples/Topics/Simulate/SimpleParticleSystem/ParticleSystem.pde similarity index 100% rename from examples/Topics/Simulate/SimpleParticleSystem/ParticleSystem.pde rename to processing/mode/examples/Topics/Simulate/SimpleParticleSystem/ParticleSystem.pde diff --git a/examples/Topics/Simulate/SimpleParticleSystem/SimpleParticleSystem.pde b/processing/mode/examples/Topics/Simulate/SimpleParticleSystem/SimpleParticleSystem.pde similarity index 100% rename from examples/Topics/Simulate/SimpleParticleSystem/SimpleParticleSystem.pde rename to processing/mode/examples/Topics/Simulate/SimpleParticleSystem/SimpleParticleSystem.pde diff --git a/examples/Topics/Simulate/SmokeParticleSystem/Particle.pde b/processing/mode/examples/Topics/Simulate/SmokeParticleSystem/Particle.pde similarity index 100% rename from examples/Topics/Simulate/SmokeParticleSystem/Particle.pde rename to processing/mode/examples/Topics/Simulate/SmokeParticleSystem/Particle.pde diff --git a/examples/Topics/Simulate/SmokeParticleSystem/ParticleSystem.pde b/processing/mode/examples/Topics/Simulate/SmokeParticleSystem/ParticleSystem.pde similarity index 100% rename from examples/Topics/Simulate/SmokeParticleSystem/ParticleSystem.pde rename to processing/mode/examples/Topics/Simulate/SmokeParticleSystem/ParticleSystem.pde diff --git a/examples/Topics/Simulate/SmokeParticleSystem/SmokeParticleSystem.pde b/processing/mode/examples/Topics/Simulate/SmokeParticleSystem/SmokeParticleSystem.pde similarity index 100% rename from examples/Topics/Simulate/SmokeParticleSystem/SmokeParticleSystem.pde rename to processing/mode/examples/Topics/Simulate/SmokeParticleSystem/SmokeParticleSystem.pde diff --git a/examples/Topics/Simulate/SmokeParticleSystem/data/texture.gif b/processing/mode/examples/Topics/Simulate/SmokeParticleSystem/data/texture.gif similarity index 100% rename from examples/Topics/Simulate/SmokeParticleSystem/data/texture.gif rename to processing/mode/examples/Topics/Simulate/SmokeParticleSystem/data/texture.gif diff --git a/examples/Topics/Simulate/SmokeParticleSystem/data/texture.png b/processing/mode/examples/Topics/Simulate/SmokeParticleSystem/data/texture.png similarity index 100% rename from examples/Topics/Simulate/SmokeParticleSystem/data/texture.png rename to processing/mode/examples/Topics/Simulate/SmokeParticleSystem/data/texture.png diff --git a/examples/Topics/Simulate/SoftBody/SoftBody.pde b/processing/mode/examples/Topics/Simulate/SoftBody/SoftBody.pde similarity index 100% rename from examples/Topics/Simulate/SoftBody/SoftBody.pde rename to processing/mode/examples/Topics/Simulate/SoftBody/SoftBody.pde diff --git a/examples/Topics/Simulate/Spring/Spring.pde b/processing/mode/examples/Topics/Simulate/Spring/Spring.pde similarity index 100% rename from examples/Topics/Simulate/Spring/Spring.pde rename to processing/mode/examples/Topics/Simulate/Spring/Spring.pde diff --git a/examples/Topics/Simulate/Springs/Springs.pde b/processing/mode/examples/Topics/Simulate/Springs/Springs.pde similarity index 100% rename from examples/Topics/Simulate/Springs/Springs.pde rename to processing/mode/examples/Topics/Simulate/Springs/Springs.pde diff --git a/examples/Topics/Textures/TextureCube/TextureCube.pde b/processing/mode/examples/Topics/Textures/TextureCube/TextureCube.pde similarity index 100% rename from examples/Topics/Textures/TextureCube/TextureCube.pde rename to processing/mode/examples/Topics/Textures/TextureCube/TextureCube.pde diff --git a/examples/Topics/Textures/TextureCube/data/berlin-1.jpg b/processing/mode/examples/Topics/Textures/TextureCube/data/berlin-1.jpg similarity index 100% rename from examples/Topics/Textures/TextureCube/data/berlin-1.jpg rename to processing/mode/examples/Topics/Textures/TextureCube/data/berlin-1.jpg diff --git a/examples/Topics/Textures/TextureCube/data/uvtex.jpg b/processing/mode/examples/Topics/Textures/TextureCube/data/uvtex.jpg similarity index 100% rename from examples/Topics/Textures/TextureCube/data/uvtex.jpg rename to processing/mode/examples/Topics/Textures/TextureCube/data/uvtex.jpg diff --git a/examples/Topics/Textures/TextureCylinder/TextureCylinder.pde b/processing/mode/examples/Topics/Textures/TextureCylinder/TextureCylinder.pde similarity index 100% rename from examples/Topics/Textures/TextureCylinder/TextureCylinder.pde rename to processing/mode/examples/Topics/Textures/TextureCylinder/TextureCylinder.pde diff --git a/examples/Topics/Textures/TextureCylinder/data/berlin-1.jpg b/processing/mode/examples/Topics/Textures/TextureCylinder/data/berlin-1.jpg similarity index 100% rename from examples/Topics/Textures/TextureCylinder/data/berlin-1.jpg rename to processing/mode/examples/Topics/Textures/TextureCylinder/data/berlin-1.jpg diff --git a/examples/Topics/Textures/TextureQuad/TextureQuad.pde b/processing/mode/examples/Topics/Textures/TextureQuad/TextureQuad.pde similarity index 100% rename from examples/Topics/Textures/TextureQuad/TextureQuad.pde rename to processing/mode/examples/Topics/Textures/TextureQuad/TextureQuad.pde diff --git a/examples/Topics/Textures/TextureQuad/data/berlin-1.jpg b/processing/mode/examples/Topics/Textures/TextureQuad/data/berlin-1.jpg similarity index 100% rename from examples/Topics/Textures/TextureQuad/data/berlin-1.jpg rename to processing/mode/examples/Topics/Textures/TextureQuad/data/berlin-1.jpg diff --git a/examples/Topics/Textures/TextureSphere/TextureSphere.pde b/processing/mode/examples/Topics/Textures/TextureSphere/TextureSphere.pde similarity index 100% rename from examples/Topics/Textures/TextureSphere/TextureSphere.pde rename to processing/mode/examples/Topics/Textures/TextureSphere/TextureSphere.pde diff --git a/examples/Topics/Textures/TextureSphere/data/world32k.jpg b/processing/mode/examples/Topics/Textures/TextureSphere/data/world32k.jpg similarity index 100% rename from examples/Topics/Textures/TextureSphere/data/world32k.jpg rename to processing/mode/examples/Topics/Textures/TextureSphere/data/world32k.jpg diff --git a/examples/Topics/Textures/TextureTriangle/TextureTriangle.pde b/processing/mode/examples/Topics/Textures/TextureTriangle/TextureTriangle.pde similarity index 100% rename from examples/Topics/Textures/TextureTriangle/TextureTriangle.pde rename to processing/mode/examples/Topics/Textures/TextureTriangle/TextureTriangle.pde diff --git a/examples/Topics/Textures/TextureTriangle/data/berlin-1.jpg b/processing/mode/examples/Topics/Textures/TextureTriangle/data/berlin-1.jpg similarity index 100% rename from examples/Topics/Textures/TextureTriangle/data/berlin-1.jpg rename to processing/mode/examples/Topics/Textures/TextureTriangle/data/berlin-1.jpg diff --git a/examples/Topics/Vectors/AccelerationWithVectors/AccelerationWithVectors.pde b/processing/mode/examples/Topics/Vectors/AccelerationWithVectors/AccelerationWithVectors.pde similarity index 100% rename from examples/Topics/Vectors/AccelerationWithVectors/AccelerationWithVectors.pde rename to processing/mode/examples/Topics/Vectors/AccelerationWithVectors/AccelerationWithVectors.pde diff --git a/examples/Topics/Vectors/AccelerationWithVectors/Mover.pde b/processing/mode/examples/Topics/Vectors/AccelerationWithVectors/Mover.pde similarity index 100% rename from examples/Topics/Vectors/AccelerationWithVectors/Mover.pde rename to processing/mode/examples/Topics/Vectors/AccelerationWithVectors/Mover.pde diff --git a/examples/Topics/Vectors/BouncingBall/BouncingBall.pde b/processing/mode/examples/Topics/Vectors/BouncingBall/BouncingBall.pde similarity index 100% rename from examples/Topics/Vectors/BouncingBall/BouncingBall.pde rename to processing/mode/examples/Topics/Vectors/BouncingBall/BouncingBall.pde diff --git a/examples/Topics/Vectors/Normalize/Normalize.pde b/processing/mode/examples/Topics/Vectors/Normalize/Normalize.pde similarity index 100% rename from examples/Topics/Vectors/Normalize/Normalize.pde rename to processing/mode/examples/Topics/Vectors/Normalize/Normalize.pde diff --git a/examples/Topics/Vectors/VectorMath/VectorMath.pde b/processing/mode/examples/Topics/Vectors/VectorMath/VectorMath.pde similarity index 100% rename from examples/Topics/Vectors/VectorMath/VectorMath.pde rename to processing/mode/examples/Topics/Vectors/VectorMath/VectorMath.pde diff --git a/processing/mode/examples/Topics/Wallpapers/Circles/Circles.pde b/processing/mode/examples/Topics/Wallpapers/Circles/Circles.pde new file mode 100644 index 000000000..4a66fb071 --- /dev/null +++ b/processing/mode/examples/Topics/Wallpapers/Circles/Circles.pde @@ -0,0 +1,17 @@ +void setup() { + fullScreen(P2D); + noStroke(); + background(255); +} + +void draw() { + if (mousePressed) { + background(255); + } + float x = random(width); + float y = random(height); + color c = color(random(255), random(255), random(255)); + float r = random(10, 30); + fill(c); + ellipse(x, y, r, r); +} \ No newline at end of file diff --git a/processing/mode/examples/Topics/Wallpapers/Circles/code/sketch.properties b/processing/mode/examples/Topics/Wallpapers/Circles/code/sketch.properties new file mode 100644 index 000000000..181123ebe --- /dev/null +++ b/processing/mode/examples/Topics/Wallpapers/Circles/code/sketch.properties @@ -0,0 +1 @@ +component=wallpaper diff --git a/processing/mode/examples/Topics/Watchfaces/WatchFace/WatchFace.pde b/processing/mode/examples/Topics/Watchfaces/WatchFace/WatchFace.pde new file mode 100644 index 000000000..78285839d --- /dev/null +++ b/processing/mode/examples/Topics/Watchfaces/WatchFace/WatchFace.pde @@ -0,0 +1,28 @@ +float angle = 0; + +void setup() { + fullScreen(P2D); + frameRate(15); +} + +void draw() { + translate(0, +wearInsets().bottom/2); + + if (wearAmbient()) { + background(0); + stroke(255); + noFill(); + } else { + background(157); + stroke(0); + fill(255); + } + + line(0, 0, width, height); + line(width, 0, 0, height); + + translate(width/2, height/2); + rotate(angle); + rect(-50, -50, 100, 100); + angle += 0.01; +} \ No newline at end of file diff --git a/processing/mode/examples/Topics/Watchfaces/WatchFace/code/sketch.properties b/processing/mode/examples/Topics/Watchfaces/WatchFace/code/sketch.properties new file mode 100644 index 000000000..a70fa2ba6 --- /dev/null +++ b/processing/mode/examples/Topics/Watchfaces/WatchFace/code/sketch.properties @@ -0,0 +1 @@ +component=watchface diff --git a/processing/mode/icons/launcher_144.png b/processing/mode/icons/launcher_144.png new file mode 100644 index 000000000..78fed6592 Binary files /dev/null and b/processing/mode/icons/launcher_144.png differ diff --git a/processing/mode/icons/launcher_192.png b/processing/mode/icons/launcher_192.png new file mode 100644 index 000000000..48d3a34fe Binary files /dev/null and b/processing/mode/icons/launcher_192.png differ diff --git a/processing/mode/icons/launcher_36.png b/processing/mode/icons/launcher_36.png new file mode 100644 index 000000000..e276bbd6f Binary files /dev/null and b/processing/mode/icons/launcher_36.png differ diff --git a/processing/mode/icons/launcher_48.png b/processing/mode/icons/launcher_48.png new file mode 100644 index 000000000..9dde1a58f Binary files /dev/null and b/processing/mode/icons/launcher_48.png differ diff --git a/processing/mode/icons/launcher_72.png b/processing/mode/icons/launcher_72.png new file mode 100644 index 000000000..ff769c587 Binary files /dev/null and b/processing/mode/icons/launcher_72.png differ diff --git a/processing/mode/icons/launcher_96.png b/processing/mode/icons/launcher_96.png new file mode 100644 index 000000000..d9b25dba2 Binary files /dev/null and b/processing/mode/icons/launcher_96.png differ diff --git a/processing/mode/icons/preview_circular.png b/processing/mode/icons/preview_circular.png new file mode 100644 index 000000000..40a11bf0d Binary files /dev/null and b/processing/mode/icons/preview_circular.png differ diff --git a/processing/mode/icons/preview_rectangular.png b/processing/mode/icons/preview_rectangular.png new file mode 100644 index 000000000..9c9d9ed41 Binary files /dev/null and b/processing/mode/icons/preview_rectangular.png differ diff --git a/processing/mode/keywords.txt b/processing/mode/keywords.txt new file mode 100644 index 000000000..b16ac21d4 --- /dev/null +++ b/processing/mode/keywords.txt @@ -0,0 +1,50 @@ +# Android-specific keywords + +# For an explanation of these tags, see Token.java +# processing/app/src/processing/app/syntax/Token.java + +VR LITERAL2 +AR LITERAL2 +STEREO LITERAL2 +MONO LITERAL2 +PORTRAIT LITERAL2 +LANDSCAPE LITERAL2 +displayDensity KEYWORD4 + +orientation FUNCTION1 +hasPermission FUNCTION1 +requestPermission FUNCTION1 +touches KEYWORD4 +touchEnded FUNCTION4 +touchMoved FUNCTION4 +touchStarted FUNCTION4 +closeKeyboard FUNCTION1 +openKeyboard FUNCTION1 +wallpaperHomeCount FUNCTION1 +wallpaperOffset FUNCTION1 +wallpaperPreview FUNCTION1 +wearAmbient FUNCTION1 +wearBurnIn FUNCTION1 +wearInsets FUNCTION1 +wearInteractive FUNCTION1 +wearLowBit FUNCTION1 +wearRound FUNCTION1 +wearSquare FUNCTION1 +cameraUp FUNCTION1 +eye FUNCTION1 +getEyeMatrix FUNCTION1 +getObjectMatrix FUNCTION1 +getRayFromScreen FUNCTION1 +intersectsSphere FUNCTION1 +intersectsBox FUNCTION1 +intersectsPlane FUNCTION1 +calculate FUNCTION1 +push FUNCTION1 +pop FUNCTION1 +circle FUNCTION1 +square FUNCTION1 + +VRCamera KEYWORD5 +ARTracker KEYWORD5 +ARTrackable KEYWORD5 +ARAnchor KEYWORD5 diff --git a/processing/mode/languages/mode.properties b/processing/mode/languages/mode.properties new file mode 100644 index 000000000..a87ae41d9 --- /dev/null +++ b/processing/mode/languages/mode.properties @@ -0,0 +1,328 @@ + + +# --------------------------------------- +# Language: English (en) (default) +# --------------------------------------- + + +# --------------------------------------- +# Menu + +# | File | Edit | Sketch | Android | Tools | Help | +# | File | + +menu.sketch.stop = Stop +menu.file.new = New +menu.file.open = Open +menu.file.save = Save + +menu.file.export_signed_package = Export Signed Package +menu.file.export_signed_bundle = Export Signed Bundle +menu.file.export_android_project = Export Android Project + + +# | File | Edit | Sketch | Android | Tools | Help | +# | Sketch | + +menu.sketch.run_on_device = Run on Device +menu.sketch.run_in_emulator = Run in Emulator + +# | File | Edit | Sketch | Android | Tools | Help | +# | Android | + +menu.android = Android +menu.android.sketch_permissions = Sketch Permissions +menu.android.app = App +menu.android.wallpaper = Wallpaper +menu.android.watch_face = Watch Face +menu.android.vr = VR +menu.android.ar = AR +menu.android.devices = Devices +menu.android.devices.no_connected_devices = No connected devices +menu.android.sdk_updater = SDK Updater +menu.android.reset_adb = Reset ADB + +# | File | Edit | Sketch | Android | Tools | Help | +# | Help | + +menu.help.processing_for_android_site = Processing for Android Site +menu.help.android_developer_site = Android Developer Site + +# --------------------------------------- +# Android Build + +android_build.error.build_folder = Build folder: %s +android_build.error.export_file_does_not_exist = "%s" is mentioned in export.txt, but it's a big fat lie and does not exist. +android_build.error.cannot_create_build_folder = Cannot create temp dir "%s" to build android sketch +android_build.error.zip_files_not_allowed = .zip files are not allowed in Android libraries.\nPlease rename "%s to be a .jar file. +android_build.error.cannot_copy_icons = Problem while copying icons. +android_build.error.cannot_create_icon_folder = Could not create "%s" folder +android_build.warn.cannot_find_zipalign.title = Cannot find zipaling... +android_build.warn.cannot_find_zipalign.body = The zipalign build tool needed to prepare the export bundle is missing.\nMake sure that your Android SDK was downloaded correctly. + +# --------------------------------------- +# Android Debugger + +android_debugger.info.attaching_debugger = Attaching debugger... +android_debugger.info.debugger_attached = Debugger attached +android_debugger.error.debugger_exception = Debugger error: %s + +# --------------------------------------- +# Android Editor + +android_editor.status.exporting_project = Exporting an Android project of the sketch... +android_editor.status.project_export_completed = Done with project export. +android_editor.status.project_export_failed = Error with project export. +android_editor.status.exporting_package = Exporting signed package... +android_editor.status.package_export_completed = Done with package export. +android_editor.status.package_export_failed = Error with package export. +android_editor.status.exporting_bundle = Exporting signed bundle... +android_editor.status.bundle_export_completed = Done with bundle export. +android_editor.status.bundle_export_failed = Error with bundle export. + +android_editor.error.cannot_create_sketch_properties = Error While creating sketch properties file "%s": %s + +# --------------------------------------- +# Android Keystore + +android_keystore.warn.cannot_create_folders.title = Folders, folders, folders +android_keystore.warn.cannot_create_folders.body = Could not create the necessary folders to build.\nPerhaps you have some file permissions to sort out? +android_keystore.warn.cannot_find_keystore.title = Well, this is unexpected... +android_keystore.warn.cannot_find_keystore.body = The keystore was succesfully created but cannot be found.\nPerhaps was it deleted accidentally? +android_keystore.error.cannot_create_keystore = The keystore could not be created, due to the following error: + +# --------------------------------------- +# Android Mode + +android_mode.dialog.watchface_debug_title = Is the watch connected to the computer? +android_mode.dialog.watchface_debug_body = Processing will install watch faces on a smartwatch either over Wi-Fi or via Bluetooth, in which case the watch needs to be paired with a phone.

    Read this guide on debugging an Android Wear App for more details. +android_mode.dialog.wallpaper_installed_title = Wallpaper installed! +android_mode.dialog.wallpaper_installed_body = Processing just built and installed your sketch as a live wallpaper on the selected device.

    You need to open the wallpaper picker in the device in order to select it as the new background. +android_mode.dialog.watchface_installed_title = Watch face installed! +android_mode.dialog.watchface_installed_body = Processing just built and installed your sketch as a watch face on the selected device.

    You need to add it as a favourite watch face on the device and then select it from the watch face picker in order to run it. +android_mode.dialog.cannot_export_package_title = Cannot complete export... +android_mode.dialog.cannot_export_package_body = The sketch still has the default package name. Not good, since this name will uniquely identify your app on the Play store... for ever! Come up with a different package name and write in the AndroidManifest.xml file in the sketch folder, after the "package=" attribute inside the manifest tag, which also contains version code and name. Once you have done that, try exporting the sketch again.

    For more info on distributing apps from Processing,
    check this online tutorial. +android_mode.dialog.cannot_use_default_icons_title = Cannot complete export... +android_mode.dialog.cannot_use_default_icons_body = The sketch does not include all required app icons. Processing could use its default set of Android icons, which are okay to test the app on your device, but a bad idea to distribute it on the Play store. Create a full set of unique icons for your app, and copy them into the sketch folder. Once you have done that, try exporting the sketch again.

    For more info on distributing apps from Processing,
    check this online tutorial. +android_mode.warn.cannot_load_sdk_title = Bad news... +android_mode.warn.cannot_load_sdk_body = The Android SDK could not be loaded.\nThe Android Mode will be disabled. +android_mode.info.cannot_open_sdk_path = "Android SDK path couldn't be opened."; +android_mode.error.cannot_create_avd = "Could not create a virtual device for the emulator."; +android_mode.error.emulator_installation_failed = "Encountered Issues with the emulator installation. Result Code is non-zero"; +android_mode.dialog.no_devices_found_title = No devices found! +android_mode.dialog.no_devices_found_body = Processing did not find any device where to run\nyour sketch on. Make sure that your handheld or\nwearable is properly connected to the computer\nand that USB or Bluetooth debugging is enabled. +android_mode.status.no_devices_found = No devices found. +android_mode.status.starting_project_build = Starting build... +android_mode.status.building_project = Building Android project... +android_mode.status.project_build_failed = Build failed. +android_mode.status.downloading_emulator = Downloading Emulator... +android_mode.status.downloading_emulator_successful = Emulator installation was successful. + +# --------------------------------------- +# Android Preprocessor + +android_preprocessor.error.cannot_parse_size = More about the size() command on Android can be\nfound here: http://wiki.processing.org/w/Android +android_preprocessor.error.cannot_parse_size_exception = Could not parse the size() command. +android_preprocessor.error.cannot_parse_smooth = More about the smooth() command on Android can be\nfound here: http://wiki.processing.org/w/Android +android_preprocessor.error.cannot_parse_smooth_exception = Could not parse the smooth() command. +android_preprocessor.warn.cannot_find_smooth_level_title = Could not find smooth level +android_preprocessor.warn.cannot_find_smooth_level_body = The smooth level of this applet could not automatically\nbe determined from your code. Use only a numeric\nvalue (not variables) for the smooth() command.\nSee the smooth() reference for an explanation. + +# --------------------------------------- +# Android Runner + +android_runner.status.waiting_for_device = Waiting for %s to become available... +android_runner.status.lost_connection_with_device = Lost connection with %s while launching. Try again. +android_runner.status.cannot_install_sketch = Could not install the sketch. +android_runner.warn.non_watch_device_title = Selected device is not a watch... +android_runner.warn.non_watch_device_body = You are trying to install a watch face on a non-watch device.\n" + "Select the correct device, or use the emulator. +android_runner.warn.watch_device_title = Selected device is a watch... +android_runner.warn.watch_device_body = You are trying to install a non-watch app on a watch. Select the correct device, or use the emulator. +android_runner.status.installing_sketch = Installing sketch on %s +android_runner.status.lost_connection = Lost connection with %s while installing. Try again. +android_runner.status.sketch_installed = Sketch installed +android_runner.status.cannot_install_sketch = Could not install the sketch. +android_runner.status.launching_sketch = Starting sketch on %s +android_runner.status.sketch_launched= Sketch installed +android_runner.status.cannot_launch_sketch = Could not start the sketch. +android_runner.status.in_emulator = in the emulator +android_runner.status.on_device = on the device +android_runner.status.cancel_waiting_for_device = No, on second thought, I'm giving up on waiting for that device to show up. +android_runner.error.cannot_parse_stacktrace = Can't parse this exception line: +android_runner.status.unknwon_exception = Unknown exception + +# --------------------------------------- +# Android SDK + +android_sdk.dialog.found_installed_sdk_title = Found an Android SDK! +android_sdk.dialog.found_installed_sdk_body = Processing found a valid Android SDK that seems to be in use already. Processing could use this SDK too, or download a new one.

    Sharing the same SDK across different development tools, like Processing and Android Studio, will save space (the SDK may use up to several GBs), but when one tool updates the SDK, it can create problems in the other. If Processing downloads a new SDK, it will keep it separate from the one it just found.

    What do you want to do? +android_sdk.option.use_existing_sdk = Use existing SDK +android_sdk.option.download_new_sdk = Download new SDK +android_sdk.dialog.cannot_find_sdk_title = Cannot find an Android SDK... +android_sdk.dialog.cannot_find_sdk_body = Processing did not find an Android SDK on this computer. If there is one, and you know where it is, click "Locate SDK path" to select it, or "Download SDK" to let Processing download the SDK automatically.

    If you want to download the SDK manually, you can get the command line tools from here. Make sure to install the SDK platform for API %s. +android_sdk.dialog.invalid_sdk_title = Android SDK is not valid... +android_sdk.dialog.invalid_sdk_body = Processing found an Android SDK, but is not valid. It could be missing some files, or might not be including the required platform for API %s.

    If a valid SDK is available in a different location, click "Locate SDK path" to select it, or "Download SDK" to let Processing download the SDK automatically.

    If you want to download the SDK manually, you can get the command line tools from here. Make sure to install the SDK platform for API %s. +android_sdk.option.download_sdk = Download SDK automatically +android_sdk.option.locate_sdk = Locate SDK path manually +android_sdk.dialog.download_phone_image_title = Download phone system image? +android_sdk.dialog.download_phone_image_body = The system image needed by the emulator does not appear to be installed. Do you want Processing to download and install it now? +android_sdk.dialog.download_watch_image_title = Download watch system image? +android_sdk.dialog.download_watch_image_body = The system image needed by the emulator does not appear to be installed. Do you want Processing to download and install it now? +android_sdk.dialog.select_sdk_folder = Choose the location of the Android SDK +android_sdk.error.sdk_selection_canceled = User canceled attempt to find SDK +android_sdk.error.sdk_download_canceled = User canceled SDK download +android_sdk.error.sdk_download_failed = SDK could not be downloaded +android_sdk.dialog.sdk_installed_title = SDK installed! +android_sdk.dialog.sdk_installed_body = Processing just downloaded and installed the Android SDK successfully. The Android mode is now ready to use!

    For documentation, examples, and tutorials, visit the Processing for Android website, and if you updated from version 3 of the mode, check the what's new page. +android_sdk.dialog.install_usb_driver =

    If you are planning to use Google Nexus devices, then you need the Google USB Driver to connect them to Processing. You will have to install the driver manually following these instructions.

    The installation files are available in this folder:
    %s +android_sdk.dialog.sdk_license_rejected_title = SDK license not accepted +android_sdk.dialog.sdk_license_rejected_body = The Android SDK was installed, but will not be usable. You can accept the license at a later time by opening a terminal, changing to the SDK folder, and then running the following command:

    tools/bin/sdkmanager --licenses +android_sdk.dialog.32bit_system_title = System is 32 bit... +android_sdk.dialog.32bit_system_body = The Android SDK no longer includes 32 bit platform tools (adb, etc.), and so they will not work.

    This thread provides some possible workarounds. +android_sdk.error.emulator_download_canceled = User canceled emulator download +android_sdk.error.emulator_download_failed = Emulator could not be downloaded +android_sdk.dialog.using_existing_sdk_title = SDK configured! +android_sdk.dialog.using_existing_sdk_body = Processing will use the existing Android SDK. The Android mode is now ready to use!

    For documentation, examples, and tutorials, visit the Processing for Android website, and if you updated from version 3 of the mode, check the what's new page. +android_sdk.dialog.accept_sdk_license_title = Accept SDK license? +android_sdk.dialog.accept_sdk_license_body = You need to accept the terms of the Android SDK license from Google in order to use the SDK. Read the license from here. +android_sdk.warn.cannot_run_adb_title = Trouble with adb! +android_sdk.warn.cannot_run_adb_body = Could not run the adb tool from the Android SDK.\nOne possibility is that its executable permission\nis not properly set. You can try setting this\npermission manually, or re-installing the SDK.\n\nThe mode will be disabled until this problem is fixed.\n +android_sdk.error.missing_sdk_folder = %s does not exist +android_sdk.error.missing_cmdtools_folder_found_sdktools = There is no cmdline-tools/latest folder in %s and SDK Tools(sdk/tools) got deprecated.\nInstall cmdline-tools in the existing SDK specifically or Create New SDK. +android_sdk.error.missing_cmdtools_folder = There is no `cmdline-tools/latest` folder in %s \nInstall cmdline-tools in the existing SDK specifically or Create New SDK. +android_sdk.error.missing_platform_tools_folder = There is no platform-tools folder in %s +android_sdk.error.missing_build_tools_folder = There is no build-tools folder in %s +android_sdk.error.missing_platforms_folder = There is no platforms folder in %s +android_sdk.error.missing_target_platform = There is no Android %s in %s +android_sdk.error.missing_android_jar = android.jar for plaform %s is missing from %s +android_sdk.error.missing_emulator = The emulator files are missing +android_debugger.info.removing_expired_keystore = Removing expired debug.keystore file. +android_debugger.error.cannot_remove_expired_keystore = Could not remove the expired debug.keystore file. +android_debugger.error.request_removing_keystore = Please remove the file %s +android_debugger.error.invalid_keystore_timestamp = The date '%s' could not be parsed. +android_debugger.error.request_bug_report = Please report this as a bug so we can fix it. + +# --------------------------------------- +# AVD + +android_avd.error.cannot_create_avd_title = Could not create the AVD +android_avd.error.cannot_create_avd_body = The default Android emulator could not be set up. Make sure
    that the Android SDK is installed properly, and that the
    system images are installed for level %s.
    (Between you and me, occasionally, this error is a red herring,
    and your sketch may be launching shortly.) + +android_avd.error.cannot_load_avd_title = Could not load the AVD +android_avd.error.cannot_load_avd_body = This could mean that the Android tools need to be updated,
    or that the Processing AVD should be deleted (it will
    automatically re-created the next time you run Processing).

    You can use the avdmanager command line tool to create AVDs manually and list the current AVDs. + + +android_avd.error.sdk_wrong_install_title = The SDK is not properly installed +android_avd.error.sdk_wrong_install_body = Please re-read the installation instructions for Processing
    found in this online tutorial. + +# --------------------------------------- +# Devices + +android_devices.error.cannot_get_device_list = Received unfamiliar output from \u201Cadb devices\u201D.\nThe device list may have errors. + +android_devices.error.no_permissions_title = Found devices with no permissions! + +android_devices.error.no_permissions_body = Make sure that the device has USB debugging enabled, and that the required USB drivers are installed on Windows, and that permissions are properly configured on Linux. Also, on Linux, don't set the USB configuration to "charging" while debugging.

    Read this guide on running apps on hardware device for more details. + + private static final String DEVICE_PERMISSIONS_URL = + "https://developer.android.com/studio/run/device.html"; + + private static final String DEVICE_PERMISSIONS_TITLE = + ""; + + private static final String DEVICE_PERMISSIONS_MESSAGE = + +# --------------------------------------- +# Keystore manager + +keystore_manager.top_label = Please enter the information below so we can generate a private key for you.
    Fields marked bold are required, though you may consider to fill some of optional fields below those to avoid potential problems.
    More about private keys can be found here. +keystore_manager.reset_password = Reset password +keystore_manager.dialog.reset_keyboard_title = Reset password +keystore_manager.dialog.reset_keyboard_body_part1 = Are you sure you want to reset the password? +keystore_manager.dialog.reset_keyboard_body_part2 = We will have to reset the keystore to do this, which means \nyou won't be able to upload an update for your app signed with\nthe new keystore to Google Play.\n\nWe will make a backup for the old keystore. +keystore_manager.warn.cannot_remove_keystore_title = Android keystore +keystore_manager.warn.cannot_remove_keystore_body = Failed to remove keystore +keystore_manager.warn.password_missmatch_title = Passwords +keystore_manager.warn.password_missmatch_body = Keystore passwords do not match +keystore_manager.warn.short_password_title = Passwords +keystore_manager.warn.short_password_body = Keystore password should be at least 6 characters long +keystore_manager.password_label = Keystore password: +keystore_manager.repeat_password_label = Repeat keystore password: +keystore_manager.issuer_credentials_header = Keystore issuer credentials +keystore_manager.common_name_label = First and last name: +keystore_manager.organizational_unitl_label = Organizational unit: +keystore_manager.organization_name_label = Organization name: +keystore_manager.city_name_label = City or locality: +keystore_manager.state_name_label = State name: +keystore_manager.country_code_label = Country code (XX): + +# --------------------------------------- +# Manifest + +manifest.warn.cannot_handle_file_title = Error handling %s +manifest.warn.cannot_handle_file_body = Errors occurred while reading or writing %s\nwhich means lots of things are likely to stop working properly.\nTo prevent losing any data, it's recommended that you use “Save As”\n"to save a separate copy of your sketch, and then restart Processing."; + +# --------------------------------------- +# Permissions + +permissions.dialog.label = Android applications must specifically ask for permission\nto do things like connect to the internet, write a file,\nor make phone calls. When installing your application,\nusers will be asked whether they want to allow such access. +permissions.dialog.url = More about permissions can be found here. + +# --------------------------------------- +# SDK Downloader + +sdk_downloader.error_cannot_find_platform_files = Cannot find the platform files +sdk_downloader.error_cannot_find_platform_tools = Cannot find the platform-tools +sdk_downloader.error_cannot_find_build_tools = Cannot find the build-tools +sdk_downloader.error_cannot_find_tools = Cannot find the tools +sdk_downloader.error_cannot_find_emulator = Cannot find the emulator +sdk_downloader.error.cannot_unpack_platform = Error unpacking platform to "%s" +sdk_downloader.download_title = SDK download +sdk_downloader.download_sdk_label = Downloading Android SDK... + +# --------------------------------------- +# System image downloader + +sys_image_downloader.dialog.select_image_title = Choose system image type to download... +sys_image_downloader.dialog.select_image_body = The Android emulator requires a system image to run. There are two types of system images available:

    1) ARM image - slow but compatible with all computers, no extra configuration needed.

    2) x86 image - fast but compatible only with Intel CPUs, extra configuration may be needed, see this guide for more details. +sys_image_downloader.dialog.accel_images_title = Some words of caution... +sys_image_downloader.dialog.haxm_install_body = Processing will install x86 images in the emulator. These images are fast, but also need the Intel Hardware Accelerated Execution Manager (Intel HAXM).

    Processing will try to run the HAXM installer now, which may ask for your administrator password or additional permissions. +sys_image_downloader.dialog.kvm_config_body = You chose to run x86 images in the emulator. This is great but you need to configure VM acceleration on Linux using the KVM package.

    Follow these instructions to configure KVM. +sys_image_downloader.dialog.ia32libs_title = Additional setup may be required... +sys_image_downloader.dialog.ia32libs_body = Looks like you are running a 64-bit version of Linux. In order
    to create the SD card in the emulator, Processing needs the
    ia32-libs compatibility package. On Ubuntu Linux, you can
    install it by runing the following command:

    sudo apt-get install lib32stdc++6 +sys_image_downloader.option.x86_image = Use x86 image +sys_image_downloader.option.arm_image = Use ARM image +sys_image_downloader.download_title = System image download +sys_image_downloader.download_watch_label = Downloading watch system image... +sys_image_downloader.download_phone_label = Downloading phone system image... + +# --------------------------------------- +# Download strings + +download_property.change_event_total = total +download_property.change_event_downloaded = downloaded +download_prompt.cancel = Cancel download + +# --------------------------------------- +# SDK Updater tool + +sdk_updater.name_column = Package name +sdk_updater.version_column = Installed version +sdk_updater.available_column = Available update + +sdk_updater.query_message = Querying packages... + +sdk_updater.no_updates_message = No updates available +sdk_updater.one_updates_message = 1 update found! +sdk_updater.many_updates_message = "%d" updates found! + +sdk_updater.warning_failed_finding_package = Failed to find package "%s" +sdk_updater.warning_failed_computing_dependency_list = Unable to compute a complete list of dependencies. + +sdk_updater.refresh_package_message = Refreshing packages... +sdk_updater.download_package_message = Downloading available updates... +sdk_updater.download_canceled_message = Download canceled + +sdk_updater.update_button_label = Update +sdk_updater.cancel_button_label = Cancel +sdk_updater.close_button_label = Close diff --git a/processing/mode/languages/mode_ko.properties b/processing/mode/languages/mode_ko.properties new file mode 100644 index 000000000..2890bf447 --- /dev/null +++ b/processing/mode/languages/mode_ko.properties @@ -0,0 +1,31 @@ + + +# --------------------------------------- +# KOREAN (ko) +# --------------------------------------- + + +# --------------------------------------- +# Menu + +# | File | Edit | Sketch | Android | Tools | Help | +# | File | + +menu.file.export_signed_package = 서명 된 패키지 내보내기 +menu.file.export_signed_bundle = 안드로이드 번들 내보내기 +menu.file.export_android_project = 안드로이드 프로젝트 내보내기 + +# | File | Edit | Sketch | Android | Tools | Help | +# | Android | + +menu.android = 안드로이드 +menu.android.sketch_permissions = 권한 스케치 +menu.android.app = 앱 +menu.android.wallpaper = 벽지 +menu.android.watch_face = 시계 얼굴 +menu.android.vr = VR +menu.android.ar = AR +menu.android.devices = 장치들 +menu.android.devices.no_connected_devices = 연결된 기기 없음 +menu.android.sdk_updater = SDK 업데이터 +menu.android.reset_adb = ADB 재설정 diff --git a/processing/mode/languages/mode_zh-Hans.properties b/processing/mode/languages/mode_zh-Hans.properties new file mode 100644 index 000000000..a43428a19 --- /dev/null +++ b/processing/mode/languages/mode_zh-Hans.properties @@ -0,0 +1,328 @@ + + +# --------------------------------------- +# Language: Chinese (zh-Hans) +# --------------------------------------- + + +# --------------------------------------- +# Menu + +# | File | Edit | Sketch | Android | Tools | Help | +# | File | + +menu.sketch.stop = 停止 +menu.file.new = 新建 +menu.file.open = 打开 +menu.file.save = 保存 + +menu.file.export_signed_package = 导出已签名的软件包 +menu.file.export_signed_bundle = 导出已签名的捆绑包 +menu.file.export_android_project = 导出Android项目 + + +# | File | Edit | Sketch | Android | Tools | Help | +# | Sketch | + +menu.sketch.run_on_device = 运行在设备上 +menu.sketch.run_in_emulator = 在模拟器中运行 + +# | File | Edit | Sketch | Android | Tools | Help | +# | Android | + +menu.android = Android +menu.android.sketch_permissions = 画图权限 +menu.android.app = 应用 +menu.android.wallpaper = 壁纸 +menu.android.watch_face = 手表表盘 +menu.android.vr = VR +menu.android.ar = AR +menu.android.devices = 设备 +menu.android.devices.no_connected_devices = 没有连接设备 +menu.android.sdk_updater = SDK 更新器 +menu.android.reset_adb = 重置 ADB + +# | File | Edit | Sketch | Android | Tools | Help | +# | Help | + +menu.help.processing_for_android_site = Processing for Android 网站 +menu.help.android_developer_site = Android 开发者网站 + +# --------------------------------------- +# Android Build + +android_build.error.build_folder = 构建文件夹: %s +android_build.error.export_file_does_not_exist = “%s” 在 export.txt 中被提及,但它是虚假的,不存在。 +android_build.error.cannot_create_build_folder = 无法创建临时目录“%s”以构建 Android Sketch +android_build.error.zip_files_not_allowed = Android 库不允许使用 .zip 文件。\n请将 “%s” 重命名为 .jar 文件。 +android_build.error.cannot_copy_icons = 复制图标时出现问题。 +android_build.error.cannot_create_icon_folder = 无法创建 “%s” 文件夹 +android_build.warn.cannot_find_zipalign.title = 找不到 zipalign... +android_build.warn.cannot_find_zipalign.body = 需要 zipalign 构建工具来准备导出包。\n请确保您的 Android SDK 已正确下载。 + +# --------------------------------------- +# Android Debugger + +android_debugger.info.attaching_debugger = 正在附加调试器... +android_debugger.info.debugger_attached = 调试器已附加 +android_debugger.error.debugger_exception = 调试器错误:%s + +# --------------------------------------- +# Android Editor + +android_editor.status.exporting_project = 正在导出 Sketch 的 Android 项目... +android_editor.status.project_export_completed = 项目导出完成。 +android_editor.status.project_export_failed = 项目导出失败。 +android_editor.status.exporting_package = 正在导出已签名的包... +android_editor.status.package_export_completed = 包导出完成。 +android_editor.status.package_export_failed = 包导出失败。 +android_editor.status.exporting_bundle = 正在导出已签名的 bundle... +android_editor.status.bundle_export_completed = bundle 导出完成。 +android_editor.status.bundle_export_failed = bundle 导出失败。 + +android_editor.error.cannot_create_sketch_properties = 创建 Sketch 属性文件“%s”时出错:%s + +# --------------------------------------- +# Android Keystore + +android_keystore.warn.cannot_create_folders.title = 文件夹,文件夹,文件夹 +android_keystore.warn.cannot_create_folders.body = 无法创建必要的文件夹以进行构建。\n也许您需要解决一些文件权限问题吗? +android_keystore.warn.cannot_find_keystore.title = 哦,这出乎意料... +android_keystore.warn.cannot_find_keystore.body = Keystore 已成功创建,但无法找到。\n难道是意外删除了吗? +android_keystore.error.cannot_create_keystore = 无法创建 Keystore,原因如下: + +# --------------------------------------- +# Android Mode + +android_mode.dialog.watchface_debug_title = 手表是否已连接到计算机? +android_mode.dialog.watchface_debug_body = Processing 将通过 Wi-Fi 或蓝牙在智能手表上安装手表面板,此时手表需要与手机配对。

    阅读有关调试 Android Wear 应用程序的指南以获取更多详细信息。 +android_mode.dialog.wallpaper_installed_title = 壁纸已安装! +android_mode.dialog.wallpaper_installed_body = Processing 刚刚将您的 Sketch 构建并作为一款动态壁纸安装在所选设备上。

    您需要在设备中打开壁纸选择器,才能将其作为新的背景选择。 +android_mode.dialog.watchface_installed_title = 手表面板已安装! +android_mode.dialog.watchface_installed_body = Processing 刚刚将您的 Sketch 构建并作为手表面板安装在所选设备上。

    您需要将其添加为设备上最喜欢的手表面板,然后在手表面板选择器中选择它才能运行它。 +android_mode.dialog.cannot_export_package_title = 无法完成导出... +android_mode.dialog.cannot_export_package_body = Sketch 仍然使用默认软件包名称。这不好,因为此名称将永久地唯一标识您的应用程序在 Play 商店中...。想出不同的软件包名称,并在 AndroidManifest.xml 文件中的 Sketch 文件夹中写入“package=”属性内的清单标记之后。一旦您完成此操作,请再次尝试导出 Sketch。

    了解有关从 Processing 发布应用程序的更多信息,请查看此在线教程。 +android_mode.dialog.cannot_use_default_icons_title = 无法完成导出... +android_mode.dialog.cannot_use_default_icons_body = Sketch 不包含所有所需的应用程序图标。Processing 可以使用其默认的 Android 图标集在您的设备上测试应用程序,但在 Play 商店中分发它是一个糟糕的想法。为您的应用程序创建一组完整的独特图标,并将它们复制到 Sketch 文件夹中。一旦您完成此操作,请再次尝试导出 Sketch。

    了解有关从 Processing 发布应用程序的更多信息,请查看此在线教程。 +android_mode.warn.cannot_load_sdk_title = 坏消息... +android_mode.warn.cannot_load_sdk_body = 无法加载 Android SDK。\n将禁用 Android Mode。 +android_mode.info.cannot_open_sdk_path = “Android SDK 路径无法打开。”; +android_mode.error.cannot_create_avd = “无法为模拟器创建虚拟设备。”; +android_mode.error.emulator_installation_failed = “安装模拟器时遇到问题。结果代码不为零”; +android_mode.dialog.no_devices_found_title = 找不到设备! +android_mode.dialog.no_devices_found_body = Processing 没有发现任何可运行 Sketch 的设备。\n确保您的手持设备或可穿戴设备已正确连接到计算机,并且 USB 或蓝牙调试已启用。 +android_mode.status.no_devices_found = 找不到设备。 +android_mode.status.starting_project_build = 开始构建... +android_mode.status.building_project = 正在构建 Android 项目... +android_mode.status.project_build_failed = 构建失败。 +android_mode.status.downloading_emulator = 下载模拟器... +android_mode.status.downloading_emulator_successful = 模拟器安装成功。 + +# --------------------------------------- +# Android 预处理器 + +android_preprocessor.error.cannot_parse_size = 关于在 Android 上使用 size() 命令的更多信息,请参阅此处:http://wiki.processing.org/w/Android +android_preprocessor.error.cannot_parse_size_exception = 无法解析 size() 命令。 +android_preprocessor.error.cannot_parse_smooth = 关于在 Android 上使用 smooth() 命令的更多信息,请参阅此处:http://wiki.processing.org/w/Android +android_preprocessor.error.cannot_parse_smooth_exception = 无法解析 smooth() 命令。 +android_preprocessor.warn.cannot_find_smooth_level_title = 找不到平滑级别 +android_preprocessor.warn.cannot_find_smooth_level_body = 该应用程序的平滑级别无法从您的代码中自动确定。\n仅针对 smooth() 命令使用数字值(而非变量)。\n请查看 smooth() 参考文档以获取解释。 + +# --------------------------------------- +# Android Runner + +android_runner.status.waiting_for_device = 正在等待 %s 可用... +android_runner.status.lost_connection_with_device = 在启动时与 %s 的连接断开。请重试。 +android_runner.status.cannot_install_sketch = 无法安装 Sketch。 +android_runner.warn.non_watch_device_title = 所选设备不是手表... +android_runner.warn.non_watch_device_body = 您正在尝试在非手表设备上安装手表面板。请选择正确的设备或使用模拟器。 +android_runner.warn.watch_device_title = 所选设备是手表... +android_runner.warn.watch_device_body = 您正在尝试在手表上安装非手表应用程序。请选择正确的设备或使用模拟器。 +android_runner.status.installing_sketch = 正在在 %s 上安装 Sketch +android_runner.status.lost_connection = 在安装时与 %s 的连接断开。请重试。 +android_runner.status.sketch_installed = Sketch 已安装 +android_runner.status.cannot_install_sketch = 无法安装 Sketch。 +android_runner.status.launching_sketch = 正在启动 %s 上的 Sketch +android_runner.status.sketch_launched= Sketch 已启动 +android_runner.status.cannot_launch_sketch = 无法启动 Sketch。 +android_runner.status.in_emulator = 在模拟器中 +android_runner.status.on_device = 在设备上 +android_runner.status.cancel_waiting_for_device = 不,我改变主意了,我放弃等待那个 +android_runner.error.cannot_parse_stacktrace = 无法解析此异常行: +android_runner.status.unknwon_exception = 未知异常 + +# --------------------------------------- +# Android SDK + +android_sdk.dialog.found_installed_sdk_title = 发现 Android SDK! +android_sdk.dialog.found_installed_sdk_body = Processing 发现了一个有效的、似乎已经在使用的 Android SDK。Processing 也可以使用这个 SDK,或者下载一个新的。

    在不同的开发工具(如 Processing 和 Android Studio)之间共享相同的 SDK 将节省空间(SDK 可以使用几个 GB),但当一个工具更新 SDK 时,可能会在另一个工具上出现问题。如果 Processing 下载了一个新的 SDK,它将与刚刚发现的 SDK 分开。

    你想怎么办? +android_sdk.option.use_existing_sdk = 使用现有的 SDK +android_sdk.option.download_new_sdk = 下载新的 SDK +android_sdk.dialog.cannot_find_sdk_title = 找不到 Android SDK... +android_sdk.dialog.cannot_find_sdk_body = Processing 在这台计算机上没有找到 Android SDK。如果有一个 SDK,并且你知道它在哪里,请点击“定位 SDK 路径”选择它,或者点击“下载 SDK”让 Processing 自动下载 SDK。

    如果你想手动下载 SDK,可以从这里获取命令行工具:这里。确保安装 API %s 的 SDK 平台。 +android_sdk.dialog.invalid_sdk_title = Android SDK 无效... +android_sdk.dialog.invalid_sdk_body = Processing 发现了一个 Android SDK,但是它无效。可能缺少一些文件,或者可能没有包括所需的 API %s 平台。

    如果在不同的位置上有一个有效的 SDK,请点击“定位 SDK 路径”选择它,或者点击“下载 SDK”让 Processing 自动下载 SDK。

    如果你想手动下载 SDK,可以从这里获取命令行工具:这里。确保安装 API %s 的 SDK 平台。 +android_sdk.option.download_sdk = 自动下载 SDK +android_sdk.option.locate_sdk = 手动选择 SDK 路径 +android_sdk.dialog.download_phone_image_title = 下载手机系统镜像? +android_sdk.dialog.download_phone_image_body = 模拟器需要的系统镜像似乎没有安装。你想让 Processing 现在下载并安装它吗? +android_sdk.dialog.download_watch_image_title = 下载手表系统镜像? +android_sdk.dialog.download_watch_image_body = 模拟器需要的系统镜像似乎没有安装。你想让 Processing 现在下载并安装它吗? +android_sdk.dialog.select_sdk_folder = 选择 Android SDK 的位置 +android_sdk.error.sdk_selection_canceled = 用户取消了查找 SDK 的尝试 +android_sdk.error.sdk_download_canceled = 用户取消了 SDK 下载 +android_sdk.error.sdk_download_failed = 无法下载 SDK +android_sdk.dialog.sdk_installed_title = SDK 已安装! +android_sdk.dialog.sdk_installed_body = Processing 刚刚成功下载并安装了 Android SDK。Android 模式现在可以使用了!

    有关文档、示例和教程,请访问Processing for Android 网站,如果您从模式的第 3 版更新,请查看新内容页面。 +android_sdk.dialog.install_usb_driver =

    如果您计划使用 Google Nexus 设备,则需要 Google USB 驱动程序将它们连接到 Processing。您将不得不按照这些说明手动安装驱动程序。

    安装文件可在此文件夹中找到:
    %s +android_sdk.dialog.sdk_license_rejected_title = SDK 许可证未被接受 +android_sdk.dialog.sdk_license_rejected_body = Android SDK 已安装,但不能使用。您可以稍后接受许可证,方法是打开终端,切换到 SDK 文件夹,然后运行以下命令:

    tools/bin/sdkmanager --licenses +android_sdk.dialog.32bit_system_title = 系统是 32 位的... +android_sdk.dialog.32bit_system_body = Android SDK 不再包括 32 位平台工具(adb 等),因此它们将无法工作。

    这个线程提供了一些可能的解决方法。 +android_sdk.error.emulator_download_canceled = 用户取消了模拟器下载 +android_sdk.error.emulator_download_failed = 无法下载模拟器 +android_sdk.dialog.using_existing_sdk_title = SDK 配置完成! +android_sdk.dialog.using_existing_sdk_body = Processing 将使用现有的 Android SDK。Android 模式现在可以使用了!

    有关文档、示例和教程,请访问Processing for Android 网站,如果您从模式的第 3 版更新,请查看新内容页面。 +android_sdk.dialog.accept_sdk_license_title = 接受 SDK 许可证? +android_sdk.dialog.accept_sdk_license_body = 你需要接受 Google 的 Android SDK 许可证条款才能使用 SDK。阅读这里的许可证。 +android_sdk.warn.cannot_run_adb_title = adb 出了问题! +android_sdk.warn.cannot_run_adb_body = 无法运行 Android SDK 中的 adb 工具。\n一种可能是它的可执行权限\n没有正确设置。您可以尝试手动设置此\n权限,或重新安装 SDK。\n\n在解决此问题之前,该模式将被禁用。\n +android_sdk.error.missing_sdk_folder = %s 不存在 +android_sdk.error.missing_cmdtools_folder_found_sdktools = 在%s中没有cmdline-tools/latest文件夹,而SDK工具(sdk/tools)已被弃用。\n请在现有SDK中专门安装cmdline-tools,或创建新的SDK。 +android_sdk.error.missing_cmdtools_folder = 在%s中没有`cmdline-tools/latest`文件夹。\n请在现有SDK中专门安装cmdline-tools,或创建新的SDK。 +android_sdk.error.missing_platform_tools_folder = 在%s中没有platform-tools文件夹。 +android_sdk.error.missing_build_tools_folder = 在%s中没有build-tools文件夹。 +android_sdk.error.missing_platforms_folder = 在%s中没有platforms文件夹。 +android_sdk.error.missing_target_platform = 在%s中没有Android版本%s。 +android_sdk.error.missing_android_jar = 在%s中缺少平台%s的android.jar文件。 +android_sdk.error.missing_emulator = 缺少模拟器文件。 +android_debugger.info.removing_expired_keystore = 正在删除过期的debug.keystore文件。 +android_debugger.error.cannot_remove_expired_keystore = 无法删除过期的debug.keystore文件。 +android_debugger.error.request_removing_keystore = 请删除文件%s。 +android_debugger.error.invalid_keystore_timestamp = 日期“%s”无法解析。 +android_debugger.error.request_bug_report = 请报告此问题,以便我们可以修复它。 + +# --------------------------------------- +# AVD + +android_avd.error.cannot_create_avd_title = 无法创建AVD(Android虚拟设备) +android_avd.error.cannot_create_avd_body = 默认的Android模拟器无法设置。请确保Android SDK已正确安装,且系统映像已安装到级别%s。(私下里,偶尔此错误是一个误导,你的sketch可能很快就会启动。) + +android_avd.error.cannot_load_avd_title = 无法加载AVD(Android虚拟设备) +android_avd.error.cannot_load_avd_body = 这可能意味着需要更新Android工具或者Processing AVD应该被删除(下一次运行Processing时,它将自动重新创建)。您可以使用avdmanager命令行工具手动创建AVDs并列出当前AVDs。 + + +android_avd.error.sdk_wrong_install_title = SDK(软件开发工具包)未正确安装 +android_avd.error.sdk_wrong_install_body = 请重新阅读Processing的安装说明,该说明在此在线教程中找到。 + +# --------------------------------------- +# Devices + +android_devices.error.cannot_get_device_list = “adb devices”收到了陌生的输出。设备列表可能有错误。 + +android_devices.error.no_permissions_title = 发现未授权的设备! + +android_devices.error.no_permissions_body = 确保设备已启用USB调试,并在Windows上安装了所需的USB驱动程序,在Linux上正确配置了权限。此外,在调试期间,在Linux上不要将USB配置设置为“充电”。了解更多详细信息,请阅读有关在硬件设备上运行应用的指南。 + +private static final String DEVICE_PERMISSIONS_URL = + "https://developer.android.com/studio/run/device.html"; + +private static final String DEVICE_PERMISSIONS_TITLE = + ""; + +private static final String DEVICE_PERMISSIONS_MESSAGE = + +# --------------------------------------- +# 密钥库管理器 + +keystore_manager.top_label = 请填写以下信息,以便我们为您生成一个私密密钥。
    加粗的字段为必填项,不过您可能要考虑在下面的可选字段中填写一些内容,以避免潜在的问题。
    有关私密密钥的更多信息可以在此处找到。 +keystore_manager.reset_password = 重置密码 +keystore_manager.dialog.reset_keyboard_title = 重置密码 +keystore_manager.dialog.reset_keyboard_body_part1 = 您确定要重置密码吗? +keystore_manager.dialog.reset_keyboard_body_part2 = 为了进行此操作,我们将必须重置密钥库,这意味着您将无法上传使用新密钥库签名的应用程序更新到Google Play。

    我们会为旧密钥库创建一个备份。 +keystore_manager.warn.cannot_remove_keystore_title = Android密钥库 +keystore_manager.warn.cannot_remove_keystore_body = 无法删除密钥库 +keystore_manager.warn.password_missmatch_title = 密码 +keystore_manager.warn.password_missmatch_body = 密钥库密码不匹配 +keystore_manager.warn.short_password_title = 密码 +keystore_manager.warn.short_password_body = 密钥库密码应至少为6个字符 +keystore_manager.password_label = 密钥库密码: +keystore_manager.repeat_password_label = 重复输入密钥库密码: +keystore_manager.issuer_credentials_header = 密钥库颁发者凭据 +keystore_manager.common_name_label = 名称: +keystore_manager.organizational_unitl_label = 组织单位: +keystore_manager.organization_name_label = 组织名称: +keystore_manager.city_name_label = 城市或地区: +keystore_manager.state_name_label = 州省名: +keystore_manager.country_code_label = 国家代码(XX): + +# --------------------------------------- +# Manifest + +manifest.warn.cannot_handle_file_title = 处理%s时出错 +manifest.warn.cannot_handle_file_body = 在读取或写入%s时发生错误,这意味着很多东西可能无法正常工作。
    为了防止数据丢失,建议您使用“另存为”
    保存您的sketch的单独副本,然后重新启动Processing。 + +# --------------------------------------- +# Permissions + +permissions.dialog.label = Android应用程序必须明确请求权限
    进行诸如连接到互联网、写入文件或拨打电话等操作。
    在安装您的应用程序时,用户将被询问是否允许这样的访问。 +permissions.dialog.url = 有关权限的更多信息可以在此处找到。 + +# --------------------------------------- +# SDK Downloader + +sdk_downloader.error_cannot_find_platform_files = 找不到平台文件 +sdk_downloader.error_cannot_find_platform_tools = 找不到平台工具 +sdk_downloader.error_cannot_find_build_tools = 找不到构建工具 +sdk_downloader.error_cannot_find_tools = 找不到工具 +sdk_downloader.error_cannot_find_emulator = 找不到模拟器 +sdk_downloader.error.cannot_unpack_platform = 解压平台至“%s”时出错 +sdk_downloader.download_title = SDK下载 +sdk_downloader.download_sdk_label = 正在下载Android SDK… + +# --------------------------------------- +# System image downloader + +sys_image_downloader.dialog.select_image_title = 选择要下载的系统镜像类型… +sys_image_downloader.dialog.select_image_body = Android模拟器需要系统镜像才能运行。有两种系统镜像可用:

    1) ARM镜像——速度慢,但兼容所有计算机,没有额外配置需要。

    2) x86镜像——速度快,但仅兼容Intel CPU,可能需要额外的配置,请参阅此指南了解更多详细信息。 +sys_image_downloader.dialog.accel_images_title = 几点忠告… +sys_image_downloader.dialog.haxm_install_body = Processing将在模拟器中安装x86映像。这些映像很快,但也需要Intel的硬件加速执行管理器(Intel HAXM)。

    Processing现在将尝试运行HAXM安装程序,该程序可能会要求您输入管理员密码或其他权限。 +sys_image_downloader.dialog.kvm_config_body = 您选择在模拟器中运行x86映像。这很棒,但您需要在Linux上使用KVM包配置VM加速。

    请按照这些说明配置KVM。 +sys_image_downloader.dialog.ia32libs_title = 可能需要进行其他设置… +sys_image_downloader.dialog.ia32libs_body = 看起来您正在运行64位版本的Linux。为了
    在模拟器中创建SD卡,Processing需要
    ia32-libs兼容性包。在Ubuntu Linux上,您可以
    通过运行以下命令安装它:

    sudo apt-get install lib32stdc++6 +sys_image_downloader.option.x86_image = 使用x86镜像 +sys_image_downloader.option.arm_image = 使用ARM镜像 +sys_image_downloader.download_title = 系统镜像下载 +sys_image_downloader.download_watch_label = 下载手表系统镜像… +sys_image_downloader.download_phone_label = 下载手机系统镜像… + +# --------------------------------------- +# Download strings + +download_property.change_event_total = 总计 +download_property.change_event_downloaded = 已下载 +download_prompt.cancel = 取消下载 + +# --------------------------------------- +# SDK Updater tool + +sdk_updater.name_column = 包名称 +sdk_updater.version_column = 已安装版本 +sdk_updater.available_column = 可用更新 + +sdk_updater.query_message = 正在查询套件… + +sdk_updater.no_updates_message = 没有可用的更新 +sdk_updater.one_updates_message = 发现1个更新! +sdk_updater.many_updates_message = 发现“%d”个更新! + +sdk_updater.warning_failed_finding_package = 无法找到包“%s” +sdk_updater.warning_failed_computing_dependency_list = 无法计算完整的依赖关系列表。 + +sdk_updater.refresh_package_message = 正在刷新套件… +sdk_updater.download_package_message = 正在下载可用更新… +sdk_updater.download_canceled_message = 下载已取消 + +sdk_updater.update_button_label = 更新 +sdk_updater.cancel_button_label = 取消 +sdk_updater.close_button_label = 关闭 diff --git a/processing/mode/libraries/ar/README.md b/processing/mode/libraries/ar/README.md new file mode 100644 index 000000000..8dddfce92 --- /dev/null +++ b/processing/mode/libraries/ar/README.md @@ -0,0 +1,21 @@ +![Image](imgs/bg_1.png) + +# AR library for Processing-Android + +![Android](https://img.shields.io/badge/platform-Android-green.svg?longCache=true&style=for-the-badge) ![ARCore](https://img.shields.io/badge/ARCore-v1.2.0-blue.svg?longCache=true&style=for-the-badge) ![In Progress](https://img.shields.io/badge/in--progress-true-green.svg?longCache=true&style=for-the-badge)
    +This library includes ARCore renderer to create AR apps using Processing. + +## Steps to build: +* Make sure you have both [Processing](https://github.com/processing/processing) and [Processing-Android](https://github.com/processing/processing-android) built before you proceed.
    +* For Building [Processing-Android](https://github.com/processing/processing-android) refer [Wiki](https://github.com/processing/processing-android/wiki/Building-Processing-for-Android).
    +* Once built, clone [processing-ar](https://github.com/SyamSundarKirubakaran/processing-ar) into the `Libraries` Directory right next to `vr` Directory.
    +NOTE: Rename the cloned directory as `ar` and the name of the module to be `processing-ar`.
    +* Make sure to import `processing-ar` as a module in your IDE.
    +* Build it using the `ant` command through terminal and on Successful build, you'll see `ar.jar` file appear under `libraries/ar/library`.
    +* Once built, hit Run. You'll see `AR` appear under `Sketch -> Import Library... -> AR`.
    +* On clicking it, you'll get an import to the AR Library as `import processing.ar.*;`.
    + +## Working: +

    + +

    \ No newline at end of file diff --git a/processing/mode/libraries/ar/build.gradle b/processing/mode/libraries/ar/build.gradle new file mode 100644 index 000000000..5b47b90b4 --- /dev/null +++ b/processing/mode/libraries/ar/build.gradle @@ -0,0 +1,123 @@ +import java.nio.file.Files +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +plugins { + id 'java-library' + id 'maven-publish' +} + +dependencies { + compileOnly name: "android" + compileOnly "org.p5android:processing-core:${modeVersion}" + implementation "com.google.ar:core:${garVersion}" +} + +sourceSets { + main { + java.srcDir("../../../../libs/processing-ar/src/main/java/") + resources { + srcDir("../../../../libs/processing-ar/src/main/") + exclude "AndroidManifest.xml" + exclude "**/java/**" + } + } +} + +java { + withSourcesJar() +} + +tasks.named('jar') { + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + +tasks.register("sourceJar", Jar) { + from sourceSets.main.allJava + archiveClassifier.set("sources") +} + +// Does not work because of Processing-specific tags in source code, such as @webref +tasks.register("javadocJar", Jar) { + dependsOn javadoc + archiveClassifier.set("javadoc") + from javadoc.destinationDir +} + +artifacts { +// archives javadocJar + archives sourceJar +} + +tasks.jar { + doLast { + ant.checksum file: archiveFile.get().asFile + } +} + +tasks.clean { + doFirst { + delete "dist" + delete "library/ar.jar" + } +} + +tasks.compileJava { + doFirst { + String[] deps = ["core.jar"] + File libFolder = file("library") + libFolder.mkdirs() + deps.each { String fn -> + Files.copy( + file("${rootDir}/build/libs/" + fn).toPath(), + file("library/" + fn).toPath(), + REPLACE_EXISTING + ) + } + } +} + +tasks.build { + doLast { + // Copying ar jar to library folder + File arJar = file("library/ar.jar") + arJar.mkdirs() + + // Need to check the existance of the files before using as the files + // will get generated only if Task ':mode:libraries:ar:jar' is not being skipped + // Task ':mode:libraries:ar:jar' will be skipped if source files are unchanged or jar task is UP-TO-DATE + def arJarFile = file("$buildDir/libs/ar.jar") + if (arJarFile.exists()) { + Files.copy(arJarFile.toPath(), arJar.toPath(), REPLACE_EXISTING) + } + + // Rename artifacts for Maven publishing + def processingArJar = file("$buildDir/libs/processing-ar-${arLibVersion}.jar") + if (arJarFile.exists()) { + Files.move(arJarFile.toPath(), processingArJar.toPath(), REPLACE_EXISTING) + } + + def processingArSourcesJar = file("$buildDir/libs/processing-ar-${arLibVersion}-sources.jar") + def arSourcesJar = file("$buildDir/libs/ar-sources.jar") + if (arSourcesJar.exists()) { + Files.move(arSourcesJar.toPath(), processingArSourcesJar.toPath(), REPLACE_EXISTING) + } + + def arMd5File = file("$buildDir/libs/ar.jar.MD5") + def processingArMd5File = file("$buildDir/libs/processing-ar-${arLibVersion}.jar.md5") + if (arMd5File.exists()) { + Files.move(arMd5File.toPath(), processingArMd5File.toPath(), REPLACE_EXISTING) + } + } +} + +ext { + libName = 'processing-ar' + libVersion = arLibVersion + libJar = "${buildDir}/libs/${libName}-${libVersion}.jar" + libSrc = "${buildDir}/libs/${libName}-${libVersion}-sources.jar" + libMd5 = "${buildDir}/libs/${libName}-${libVersion}-sources.jar.md5" + libDependencies = [[group: 'org.p5android', name: 'processing-core', version: modeVersion], + [group: 'com.google.ar', name: 'core', version: garVersion]] +} + +apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/processing/mode/libraries/ar/examples/Cubes/Cubes.pde b/processing/mode/libraries/ar/examples/Cubes/Cubes.pde new file mode 100644 index 000000000..d5a61a518 --- /dev/null +++ b/processing/mode/libraries/ar/examples/Cubes/Cubes.pde @@ -0,0 +1,93 @@ +import processing.ar.*; + +ARTracker tracker; +ARAnchor touchAnchor; +ArrayList trackAnchors; +float angle; + +void setup() { + fullScreen(AR); + tracker = new ARTracker(this); + tracker.start(); + trackAnchors = new ArrayList(); +} + +void draw() { + // The AR Core session, frame and camera can be accessed through Processing's surface object + // to obtain the full information about the AR scene: +// ARSurface surface = (ARSurface) getSurface(); +// surface.camera.getPose(); +// surface.frame.getLightEstimate(); + + lights(); + + if (mousePressed) { + // Create new anchor at the current touch point + if (touchAnchor != null) touchAnchor.dispose(); + ARTrackable hit = tracker.get(mouseX, mouseY); + if (hit != null) touchAnchor = new ARAnchor(hit); + else touchAnchor = null; + } + + // Draw objects attached to each anchor + for (ARAnchor anchor : trackAnchors) { + if (anchor.isTracking()) drawBox(anchor, 255, 255, 255); + + // It is very important to dispose anchors once they are no longer tracked. + if (anchor.isStopped()) anchor.dispose(); + } + if (touchAnchor != null) drawBox(touchAnchor, 255, 0, 0); + + // Conveniency function in the tracker object to remove disposed anchors from a list + tracker.clearAnchors(trackAnchors); + + // Draw trackable planes + for (int i = 0; i < tracker.count(); i++) { + ARTrackable trackable = tracker.get(i); + if (!trackable.isTracking()) continue; + + pushMatrix(); + trackable.transform(); + if (mousePressed && trackable.isSelected(mouseX, mouseY)) { + fill(255, 0, 0, 100); + } else { + fill(255, 100); + } + beginShape(); + float[] points = trackable.getPolygon(); + for (int n = 0; n < points.length / 2; n++) { + float x = points[2 * n]; + float z = points[2 * n + 1]; + vertex(x, 0, z); + } + endShape(); + popMatrix(); + } + + angle += 0.1; +} + +void drawBox(ARAnchor anchor, int r, int g, int b) { + anchor.attach(); + fill(r, g, b); + rotateY(angle); + box(0.15f); + anchor.detach(); +} + +void trackableEvent(ARTrackable t) { + if (trackAnchors.size() < 10) { + float x0 = 0, y0 = 0; + if (t.isWallPlane()) { + // The new trackable is a wall, so adding the anchor 0.3 meters to its side + x0 = 0.3; + } else if (t.isFloorPlane()) { + // The new trackable is a floor plane, so adding the anchor 0.3 meters above it + y0 = 0.3; + } else { + // The new trackable is a floor plane, so adding the anchor 0.3 meters below it + y0 = -0.3; + } + trackAnchors.add(new ARAnchor(t, x0, y0, 0)); + } +} \ No newline at end of file diff --git a/processing/mode/libraries/ar/examples/Cubes/code/sketch.properties b/processing/mode/libraries/ar/examples/Cubes/code/sketch.properties new file mode 100644 index 000000000..db8eaa0b5 --- /dev/null +++ b/processing/mode/libraries/ar/examples/Cubes/code/sketch.properties @@ -0,0 +1 @@ +component=ar diff --git a/processing/mode/libraries/ar/examples/ImageMarkers/ImageMarkers.pde b/processing/mode/libraries/ar/examples/ImageMarkers/ImageMarkers.pde new file mode 100644 index 000000000..6b346c7f2 --- /dev/null +++ b/processing/mode/libraries/ar/examples/ImageMarkers/ImageMarkers.pde @@ -0,0 +1,43 @@ +import processing.ar.*; + +ARTracker tracker; +ARAnchor anchor; +PShape earth; + +void setup() { + fullScreen(AR); + + tracker = new ARTracker(this); + + PImage earthImg = loadImage("earth.jpg"); + tracker.start(); + + // Add the image to use as a marker to the AR tracker + tracker.addImage("earth", earthImg); + + // If you know the size (in meters) of the image in the physical space, + // you can specify it in the addImage(), this is optional but it would + // speed up the detection since the AR library will know the size of the + // marker beforehand + // tracker.addImage("earth", earthImg, 0.25); + + earth = createShape(SPHERE, 0.15); +} + +void draw() { + lights(); + + if (mousePressed) { + // Create new anchor at the current touch point + if (anchor != null) anchor.dispose(); + ARTrackable hit = tracker.get(mouseX, mouseY); + if (hit != null && hit.isImage() && hit.getName().equals("earth")) anchor = new ARAnchor(hit); + else anchor = null; + } + + if (anchor != null) { + anchor.attach(); + shape(earth); + anchor.detach(); + } +} \ No newline at end of file diff --git a/processing/mode/libraries/ar/examples/ImageMarkers/code/sketch.properties b/processing/mode/libraries/ar/examples/ImageMarkers/code/sketch.properties new file mode 100644 index 000000000..db8eaa0b5 --- /dev/null +++ b/processing/mode/libraries/ar/examples/ImageMarkers/code/sketch.properties @@ -0,0 +1 @@ +component=ar diff --git a/processing/mode/libraries/ar/examples/ImageMarkers/data/earth.jpg b/processing/mode/libraries/ar/examples/ImageMarkers/data/earth.jpg new file mode 100644 index 000000000..73ebe8431 Binary files /dev/null and b/processing/mode/libraries/ar/examples/ImageMarkers/data/earth.jpg differ diff --git a/processing/mode/libraries/ar/examples/ImportObj/ImportObj.pde b/processing/mode/libraries/ar/examples/ImportObj/ImportObj.pde new file mode 100644 index 000000000..f0412165e --- /dev/null +++ b/processing/mode/libraries/ar/examples/ImportObj/ImportObj.pde @@ -0,0 +1,31 @@ +import processing.ar.*; + +ARTracker tracker; +ARAnchor anchor; +PShape arObj; + +void setup() { + fullScreen(AR); + arObj = loadShape("model.obj"); + + tracker = new ARTracker(this); + tracker.start(); +} + +void draw() { + lights(); + + if (mousePressed) { + // Create new anchor at the current touch point + if (anchor != null) anchor.dispose(); + ARTrackable hit = tracker.get(mouseX, mouseY); + if (hit != null) anchor = new ARAnchor(hit); + else anchor = null; + } + + if (anchor != null) { + anchor.attach(); + shape(arObj); + anchor.detach(); + } +} diff --git a/processing/mode/libraries/ar/examples/ImportObj/code/sketch.properties b/processing/mode/libraries/ar/examples/ImportObj/code/sketch.properties new file mode 100644 index 000000000..db8eaa0b5 --- /dev/null +++ b/processing/mode/libraries/ar/examples/ImportObj/code/sketch.properties @@ -0,0 +1 @@ +component=ar diff --git a/processing/mode/libraries/ar/examples/ImportObj/data/grey.png b/processing/mode/libraries/ar/examples/ImportObj/data/grey.png new file mode 100644 index 000000000..b6d6f05e4 Binary files /dev/null and b/processing/mode/libraries/ar/examples/ImportObj/data/grey.png differ diff --git a/processing/mode/libraries/ar/examples/ImportObj/data/materials.mtl b/processing/mode/libraries/ar/examples/ImportObj/data/materials.mtl new file mode 100755 index 000000000..d0bf7bc00 --- /dev/null +++ b/processing/mode/libraries/ar/examples/ImportObj/data/materials.mtl @@ -0,0 +1,12 @@ +newmtl mat10 + Kd 0.30 0.69 0.31 + +newmtl mat9 + Kd 0.55 0.76 0.29 + +newmtl mat20 + Kd 0.47 0.33 0.28 + +newmtl mat21 + Kd 1.00 1.00 1.00 + diff --git a/processing/mode/libraries/ar/examples/ImportObj/data/model.obj b/processing/mode/libraries/ar/examples/ImportObj/data/model.obj new file mode 100755 index 000000000..b30f38dcd --- /dev/null +++ b/processing/mode/libraries/ar/examples/ImportObj/data/model.obj @@ -0,0 +1,949 @@ +mtllib materials.mtl +v 0.03500453 0.019117 0.02433401 +v -0.006614864 0.0148387 -0.01981938 +v 0.03608745 0.01436758 0.02320725 +v 0.008281738 -0.0277046 0.0253039 +v -0.002996296 0.01700664 -0.01070774 +v -0.02605835 0.00233531 0.005670011 +v -0.01863462 0.2030077 0.1667039 +v -0.01543677 0.0811317 0.09948444 +v 0.03643253 0.02604151 0.01268643 +v 0.01329854 0.01135957 -0.04139006 +v 0.01430747 0.006541491 -0.04051358 +v 0.03744149 0.02122343 0.01356286 +v -0.02249089 0.01276231 0.007519543 +v -0.02148193 0.007944226 0.008396029 +v 0.02063528 0.2003068 -0.1131554 +v 0.03034195 0.1211638 -0.04571742 +v 0.001753241 0.03735411 -0.02767992 +v 0.01017797 0.08989894 -0.05778307 +v 0.02250633 0.09819317 -0.04077369 +v 0.01443794 0.165741 -0.09460694 +v 0.01391521 0.04973161 0.003262162 +v 0.002539635 0.02941597 0.01170969 +v -0.01658016 0.02541399 0.0003730655 +v -0.06505209 0.1163319 -0.004948497 +v 0.01759383 -0.009375334 -0.01819491 +v 0.01072627 0.02040827 0.01247698 +v -0.03959695 0.07464659 -0.01419669 +v -0.0185844 -0.01070535 0.005561471 +v -0.05041924 0.06390738 0.001349032 +v 0.006013215 0.03756678 -0.005902886 +v 0.006297797 0.0006850958 -0.02858913 +v 0.00980702 -0.001161218 -0.02554345 +v 0.009522438 0.03572035 -0.002857208 +v -0.02055576 0.005186796 0.005080104 +v -0.01704657 0.003340483 0.008125842 +v -0.01841947 0.04425907 -0.06405324 +v -0.03397268 0.08564556 -0.1005961 +v -0.02476519 0.05102789 -0.0618062 +v 0.02960703 0.004537225 0.03208822 +v 0.01192492 -0.002894044 -0.007959068 +v 0.02648646 0.0009000301 0.03351396 +v -0.0108943 0.03334081 0.01692253 +v -0.01401487 0.02970362 0.01834822 +v 0.0001596808 0.05255258 0.02025765 +v 0.051799 0.1298349 0.04909253 +v 0.0005793571 0.01908755 0.01888919 +v 0.0499264 -0.01731193 -0.0006120801 +v 0.04912642 -0.0203712 0.00326097 +v 0.003881067 0.01530802 0.02209854 +v -0.006612957 -0.02721441 -0.02011198 +v -0.00741291 -0.03027368 -0.01623887 +v -0.002917379 -0.02342451 0.03035516 +v -0.1224723 0.1188886 -0.0985955 +v -0.1014801 0.09994781 -0.08556575 +v -0.1030209 0.1053934 0.1052268 +v -0.0212988 -0.01117647 -0.03490889 +v 0.0158276 0.02291071 -0.001223564 +v -0.07115999 0.05342448 0.04761094 +v -0.01541749 -0.022282 0.02439606 +v -0.07139879 0.05052888 0.06651038 +v -0.01759699 0.02451885 0.00341332 +v -0.005952865 0.01053989 -0.003992498 +v -0.02672911 -0.009570718 0.01931787 +v -0.02047238 -0.006453276 -0.02330488 +v -0.1268312 0.07678592 0.002777576 +v -0.1150678 0.0672816 0.009178877 +v -0.07114238 0.03764832 -0.006659627 +v -0.02572748 -0.02377486 -0.007589579 +v -0.01347169 0.01270366 0.01641744 +v 0.04988497 0.003462791 -0.04212153 +v -0.02844059 -0.002164483 -0.06671989 +v -0.02802613 -0.007144094 -0.06690043 +v 0.05029941 -0.0015167 -0.04230213 +v -0.07574981 -0.008532882 0.0003093481 +v -0.07533535 -0.01351237 0.000128746 +v -0.02666298 -0.00684154 0.06633401 +v -0.02624851 -0.01182103 0.06615347 +v 0.05098358 0.0005722046 0.04011029 +v 0.05139804 -0.004407287 0.03992969 +v -0.02176204 -0.04356194 -0.07239652 +v 0.05649534 -0.03909731 -0.04734689 +v -0.0695408 -0.04930222 -0.005644143 +v -0.02081233 -0.04838526 0.06066066 +v 0.0570823 -0.04207826 0.03488696 +v 0.07041439 0.01539612 -0.05716801 +v -0.03705421 0.007957578 -0.08661979 +v -0.03663483 0.002982736 -0.08689362 +v 0.07083377 0.01042128 -0.05744183 +v -0.04998913 0.00577414 -0.06675935 +v -0.0977459 -0.002287507 0.006567121 +v -0.09732652 -0.007262349 0.006293297 +v -0.08314592 -0.002056479 0.02473265 +v -0.04424265 -0.001441121 0.07313687 +v -0.02778679 -0.001180768 0.0936116 +v -0.02736741 -0.006155729 0.09333777 +v 0.07614198 0.00974834 0.05422109 +v 0.07656136 0.004773378 0.05394727 +v 0.0750888 0.01078677 0.03373891 +v 0.07135528 0.01446831 -0.03887033 +v -0.02881977 0.0001393557 0.0680449 +v 0.05481839 0.008709669 0.04043132 +v -0.03199118 -0.08194315 -0.09419835 +v -0.08995503 -0.08119118 0.001257837 +v -0.01710126 -0.08282828 0.0858857 +v 0.08588874 -0.0845921 0.04273254 +v 0.07668623 -0.08404505 -0.06856555 +v 0.04908967 0.01287305 -0.04398769 +v -0.03213352 0.007053494 -0.06265551 +v -0.07307526 -0.0002579689 0.00748086 +v -0.03908038 -0.01706243 -0.0657807 +v 0.04292423 -0.0108943 -0.05105788 +v 0.05283928 -0.01613939 0.03291172 +v -0.02927521 -0.02569211 0.06449127 +v -0.07646161 -0.02560854 0.006180525 +v 0.01355043 -0.02314949 0.04385048 +v 0.01901108 -0.008549571 -0.01473302 +v 0.01652125 -0.01269674 -0.0159986 +v 0.0110606 -0.02729666 0.0425849 +v -0.0290007 0.01061189 0.01693255 +v -0.03149053 0.00646472 0.01566696 +v -0.02181986 0.003832459 0.008502007 +v 0.1225296 0.1186905 0.01693249 +vn 0.6759462 0.3124644 -0.66743 +vn 0.3036638 -0.1538349 0.9402782 +vn -0.1288713 -0.6415519 -0.7561768 +vn 0.6317405 -0.4490114 -0.6318961 +vn 0.5168009 0.6132385 -0.5973738 +vn 0.6353032 -0.3483134 0.6892516 +vn -0.9574758 -0.1581962 0.2412763 +vn -0.7149026 -0.4934512 0.495399 +vn 0.513531 -0.404038 0.7569936 +vn -0.6899258 0.4684089 -0.5519016 +vn 0.2377314 0.9194574 -0.3131804 +vn -0.9323441 0.05290945 0.3576803 +vn 0.902027 0.1131086 -0.4165979 +vn -0.7814992 -0.2662952 -0.5642216 +vn -0.1205278 0.1531868 0.9808195 +vn -0.7951764 -0.1880778 -0.5764732 +vn 0.2017912 -0.9636137 0.1752966 +vn 0.9226931 -0.1570926 -0.3520787 +vn -0.1877113 0.5051754 0.8423552 +vn -0.828216 0.3726066 0.4185961 +vn 0.6965678 0.2683296 0.6654266 +vn -0.8662611 0.1966525 -0.4592596 +vn 0.9343936 -0.1576237 -0.3194739 +vn -0.3524163 -0.3926582 -0.8494835 +vn -0.4714933 0.5698856 0.6729968 +vn 0.9370085 -0.3383273 -0.08688932 +vn 0.8010244 -0.4923694 -0.3404884 +vn -0.5066848 0.5572503 0.6578318 +vn -0.730508 -0.2646336 -0.6295453 +vn 0.4703085 -0.6906323 -0.5493971 +vn 0.5243899 -0.5602476 -0.6412004 +vn -0.6460397 0.4476092 0.618287 +vn 0.7643175 0.5396877 -0.352925 +vn -0.5135508 -0.3115103 -0.7995167 +vn -0.2271142 -0.002504592 0.9738649 +vn -0.6906796 -0.2732151 -0.6695635 +vn 0.4608619 -0.5828981 0.6692057 +vn -0.6596208 -0.3198385 -0.6801499 +vn 0.3868736 0.2156837 0.8965542 +vn 0.7782366 0.5445973 -0.3126687 +vn 0.7123011 0.3717107 -0.595364 +vn -0.350459 -0.9234875 -0.156043 +vn -0.3618422 0.5517769 0.751407 +vn -0.6169685 -0.6768168 -0.4015832 +vn 0.7018433 -0.3692735 0.6091413 +vn 0.9352891 0.190618 -0.2981597 +vn -0.5437235 0.6330461 0.551015 +vn -0.7395383 -0.5798131 -0.3419064 +vn -0.7387139 -0.5767542 -0.3487927 +vn -0.7117295 -0.5995156 -0.3660905 +vn -0.2450886 -0.6918193 -0.6792037 +vn 0.7083459 -0.6808023 -0.1864253 +vn -0.577066 0.183116 -0.7959042 +vn -0.05425279 0.4044118 0.9129664 +vn -0.3655506 0.3593813 -0.858614 +vn -0.6241442 -0.7272121 0.285669 +vn 0.9166214 -0.1101973 -0.3842678 +vn -0.2199824 -0.04261146 0.9745728 +vn -0.4381971 -0.04221215 0.8978872 +vn 0.5168417 0.3057183 -0.7996317 +vn -0.4323599 -0.04563689 0.9005456 +vn -0.1379253 0.4258103 -0.8942384 +vn 0.6460316 0.5284206 0.5508313 +vn 0.3233464 -0.7739131 -0.5445234 +vn 0.9823619 -0.1736944 -0.0692488 +vn 0.3515399 -0.2111629 -0.9120472 +vn -0.1599931 -0.6118627 0.7746137 +vn 0.5969911 0.7998882 0.06148641 +vn -0.8055354 -0.5835604 0.1028103 +vn -0.5761724 0.2658848 0.7728718 +vn 0.3974828 0.776682 0.4886436 +vn -0.7156691 -0.6781162 0.1672608 +vn -0.07940822 0.7431037 -0.6644482 +vn -0.8092278 -0.5867478 -0.02962401 +vn 0.6566163 -0.02666638 0.7537534 +vn -0.8871716 -0.4543079 -0.08081483 +vn 0.7527174 -0.6295595 -0.1925396 +vn -0.8154821 -0.5704756 -0.09770723 +vn -0.4086105 -0.6716307 0.6180208 +vn -0.6949745 0.2989345 -0.6539487 +vn 0.6619559 0.163908 0.7314018 +vn 0.266736 0.6152108 -0.7418677 +vn -0.6199426 -0.7842435 -0.02516915 +vn 0.2866544 0.5899032 -0.7548798 +vn 0.8534204 -0.4676813 -0.2301036 +vn 0.3394441 0.6475117 -0.6822803 +vn -0.000973773 0.1296944 0.9915536 +vn 0.2884114 0.6099449 -0.7380963 +vn 0.3598991 0.7445212 0.5622818 +vn 0.3754486 0.790543 0.4838181 +vn 0.6365256 -0.4595098 -0.6194239 +vn 0.3754486 0.790543 0.4838181 +vn -0.639107 -0.6897317 0.3403125 +vn 0.8143744 -0.5001561 0.2943436 +vn -0.5803067 -0.4435949 -0.6829845 +vn 0.2952288 0.05915305 -0.9535937 +vn -0.8140077 -0.04675739 -0.5789691 +vn -0.7983131 -0.08805058 0.5957711 +vn 0.3206232 -0.007661377 0.9471759 +vn 0.9964691 0.08331603 -0.01038414 +vn -0.08289231 0.9959039 0.03611439 +vn 0.286491 0.1822246 -0.9405941 +vn -0.8123397 -0.04491663 -0.5814524 +vn -0.7837809 -0.2125169 0.5835445 +vn 0.3226649 -0.08544654 0.9426486 +vn 0.9876623 0.1565333 -0.004534003 +vn 0.06860351 -0.9969714 -0.03662946 +vn 0.2587442 0.07481664 -0.9630441 +vn -0.835243 -0.04022668 -0.5484077 +vn -0.7749525 -0.09967847 0.6241096 +vn 0.356296 -0.02137723 0.9341286 +vn 0.9951558 0.08646443 -0.04678641 +vn -0.08387454 0.9949704 0.05476191 +vn 0.2568304 0.09675303 -0.9616013 +vn -0.8347547 -0.04585963 -0.548709 +vn -0.7680125 -0.1630291 0.6193371 +vn 0.3605577 -0.07899904 0.9293854 +vn 0.9929608 0.1093502 -0.04551313 +vn -0.0185397 -0.9998224 -0.00338177 +vn 0.2300202 0.127853 -0.9647509 +vn 0.9940968 0.07122472 -0.08184498 +vn -0.8547598 -0.002095141 -0.5190196 +vn -0.7535963 -0.1191722 0.6464446 +vn 0.3856194 -0.03796748 0.9218765 +vn -0.08387487 0.9949704 0.05476191 +vn -0.08387746 0.9949701 0.05476293 +vn -0.08387492 0.9949704 0.054762 +vn -0.0838771 0.9949703 0.05476097 +vn 0.8370441 -0.3003807 0.4573058 +vn -0.2022253 -0.2306736 0.951785 +vn -0.9958286 0.05799839 0.07043803 +vn -0.3230657 0.1345664 -0.9367606 +vn 0.8053956 0.06682255 -0.588959 +vn 0.8852978 -0.1409901 0.4431361 +vn 0.7753266 -0.07137989 -0.6275139 +vn -0.1704701 -0.0781832 0.9822562 +vn -0.9686102 0.2132797 0.1276949 +vn -0.3726705 0.2958405 -0.8795426 +vn -0.08772954 0.9935086 0.07241636 +vn 0.8625041 -0.5040262 -0.04521547 +vn -0.3532428 0.4605826 -0.8142992 +vn -0.5092613 0.04344368 0.8595148 +vn 0.2014092 -0.6723981 0.7122606 +vn -0.4979688 -0.8294312 -0.2531227 +vn 0.7829186 -0.6168609 -0.08075375 +vn -0.2661578 0.3731634 0.8887683 +vn -0.3811645 0.5344062 -0.7544028 +vn -0.3251337 0.4688944 -0.8212346 +vt -0.1140787 0.03138284 +vt -0.1158554 0.02556534 +vt -0.1136141 0.03119799 +vt 0.09894149 0.1048713 +vt 0.1021685 0.109191 +vt 0.1016744 0.1091149 +vt -0.1066763 0.1035016 +vt -0.1121509 0.1055655 +vt -0.1115286 0.1024404 +vt -0.08475467 0.1383537 +vt -0.0901614 0.1356119 +vt -0.08420303 0.1333365 +vt -0.06537322 -0.03387522 +vt -0.06724139 -0.02905086 +vt -0.09099902 -0.03133029 +vt 0.07564665 0.128677 +vt 0.07249503 0.124302 +vt 0.09948863 0.1275901 +vt -0.005117498 0.1487897 +vt 0.006192557 0.1439615 +vt -0.01494416 0.1586518 +vt 0.08523902 -0.0517007 +vt 0.09608601 -0.04590664 +vt 0.08278457 -0.0473825 +vt 0.0740456 0.113275 +vt 0.08742387 0.1136336 +vt 0.1010457 0.1165095 +vt -0.08119667 0.1337613 +vt -0.0558333 0.1293723 +vt -0.05547386 0.1325383 +vt 0.006262578 0.003618848 +vt 0.01038755 0.0004965129 +vt 0.01137706 0.0003261221 +vt 0.0510156 0.1469026 +vt 0.05004591 0.1471631 +vt 0.04608426 0.1428577 +vt -0.0688381 0.1264659 +vt -0.07417805 0.1235963 +vt -0.07394136 0.1231558 +vt -0.06860141 0.1260255 +vt -0.1078465 0.1188627 +vt -0.1100746 0.1132248 +vt -0.1096096 0.1130411 +vt -0.1073815 0.1186789 +vt 0.1179777 0.102659 +vt 0.1160989 0.1084227 +vt 0.1156235 0.1082677 +vt 0.1175023 0.1025041 +vt -0.1057023 0.1199926 +vt -0.103169 0.1255001 +vt -0.1183577 0.1388551 +vt -0.01354274 0.07674651 +vt -0.01952282 0.07575219 +vt -0.01567168 0.07107045 +vt -0.05110518 0.1516622 +vt -0.04528297 0.1533509 +vt -0.05128602 0.162781 +vt 0.1083808 0.1214489 +vt 0.1108377 0.1159069 +vt 0.118157 0.1268701 +vt 0.06290381 0.1529554 +vt 0.06604792 0.1581991 +vt 0.06413454 0.1593991 +vt 0.1264942 0.130957 +vt 0.1174081 0.1225252 +vt 0.1132593 0.1180342 +vt 0.1123433 0.113189 +vt -0.06912652 0.1435414 +vt -0.06841803 0.1484213 +vt -0.07191654 0.1539707 +vt -0.07323746 0.1560659 +vt -0.04413793 0.1652177 +vt -0.05065675 0.1733763 +vt -0.04452227 0.154104 +vt -0.124141 0.1168212 +vt -0.1340527 0.124265 +vt -0.1310707 0.1216421 +vt 0.09981892 0.1402302 +vt 0.09372418 0.1338609 +vt 0.1062645 0.1424113 +vt -0.007361628 0.1738323 +vt -0.007709313 0.17138 +vt -0.005147806 0.1628786 +vt -0.0538327 0.161586 +vt -0.05581391 0.1605016 +vt -0.04927731 0.1539648 +vt 0.09250034 0.1317357 +vt 0.1043252 0.1442625 +vt 0.09186566 0.135595 +vt 0.09088932 0.1333186 +vt -0.1226757 0.1284759 +vt -0.1122508 0.114762 +vt -0.1163737 0.1228593 +vt -0.08792244 0.137607 +vt -0.08221005 0.1305407 +vt -0.08053256 0.1320529 +vt -0.08900896 0.1344408 +vt -0.08996293 0.1367266 +vt -0.09593607 0.1432101 +vt 0.09943064 0.1496814 +vt 0.1019548 0.1527476 +vt 0.0930742 0.1472529 +vt -0.01989863 -0.1092991 +vt -0.01381554 -0.09547608 +vt -0.01781833 -0.09712763 +vt -0.1225598 0.1129164 +vt -0.1139305 0.1103271 +vt -0.1124988 0.1144137 +vt 0.1284472 0.09043346 +vt 0.1162811 0.0925452 +vt 0.115033 0.08839886 +vt -0.1183999 0.1133674 +vt -0.1187976 0.1155082 +vt -0.1237072 0.1147289 +vt 0.05978798 0.1068567 +vt 0.05546509 0.1071068 +vt 0.05740996 0.103238 +vt -0.1209545 0.1119765 +vt -0.1204887 0.1098495 +vt -0.1123944 0.1091671 +vt 0.1132004 0.1118751 +vt 0.1213011 0.1124761 +vt 0.1266491 0.1136676 +vt 0.01676467 -0.1112485 +vt 0.01793306 -0.1064167 +vt 0.0187613 -0.09627871 +vt -0.11463 0.006658147 +vt -0.1112072 0.004005964 +vt -0.1109009 0.0044012 +vt -0.1143238 0.00705338 +vt -0.08027916 0.03211533 +vt -0.07883324 0.02803375 +vt -0.07836194 0.02820071 +vt -0.07980787 0.03228229 +vt 0.10148 0.1225743 +vt 0.1026396 0.1267463 +vt 0.1021579 0.1268802 +vt 0.1009982 0.1227082 +vt -0.113922 0.0599463 +vt -0.1136528 0.06426805 +vt -0.1196823 0.06541675 +vt 0.06852727 0.1349547 +vt 0.06428875 0.1340687 +vt 0.06717528 0.130841 +vt -0.031122 0.136667 +vt -0.02851421 0.1401238 +vt -0.03805421 0.1462688 +vt 0.08087214 0.137729 +vt 0.08012602 0.1334637 +vt 0.09210182 0.1393621 +vt -0.1228887 0.06915759 +vt -0.1220789 0.06966314 +vt -0.1272685 0.0721065 +vt -0.1220974 0.07083166 +vt -0.1229096 0.07033004 +vt -0.1171047 0.06465271 +vt -0.1166995 0.0635231 +vt -0.1226809 0.06901416 +vt -0.1271384 0.07184418 +vt -0.1225632 0.1000327 +vt -0.1170617 0.09840968 +vt -0.1109856 0.09754105 +vt -0.03230288 0.1458602 +vt -0.03669528 0.1452095 +vt -0.03228601 0.1453605 +vt -0.08946843 0.1235516 +vt -0.09427954 0.1223765 +vt -0.09411226 0.1219053 +vt 0.1168864 0.1136613 +vt 0.1120057 0.1154441 +vt 0.1118342 0.1149745 +vt 0.1167149 0.1131916 +vt -0.08923022 0.102894 +vt -0.08441637 0.1040579 +vt -0.09072792 0.1045616 +vt -0.02206336 -0.06198291 +vt -0.026459 -0.06236074 +vt -0.02417939 -0.06672869 +vt -0.05463599 0.1456433 +vt -0.0503474 0.1467942 +vt -0.04910248 0.1595716 +vt 0.1189074 0.08985645 +vt 0.1159428 0.08829741 +vt 0.1177477 0.08793834 +vt 0.1171631 0.08354659 +vt 0.1199852 0.08535087 +vt 0.123382 0.09447052 +vt -0.1099102 0.04050694 +vt -0.1141292 0.03747376 +vt -0.1123682 0.03800825 +vt 0.1142366 0.08509803 +vt 0.1171581 0.08316149 +vt 0.1234379 0.09405048 +vt -0.09879679 0.08813052 +vt -0.09228849 0.0808953 +vt -0.08597927 0.08036333 +vt 0.1120607 0.09141384 +vt 0.1117586 0.09101542 +vt 0.1165892 0.08735266 +vt -0.0806265 0.1231025 +vt -0.084486 0.1184276 +vt -0.08410042 0.1181093 +vt -0.08024094 0.1227841 +vt 0.003482211 0.1583301 +vt 0.008283757 0.1566894 +vt 0.009117588 0.1606198 +vt 0.003706832 0.1578865 +vt -0.1132746 0.1046951 +vt -0.110367 0.1100145 +vt -0.1334541 0.1032534 +vt 0.07486738 0.03010331 +vt 0.07010847 0.03385869 +vt 0.06923567 0.02785967 +vt 0.08733691 -0.001883278 +vt 0.107071 -0.006697561 +vt 0.08547853 0.003908656 +vt -0.1046513 -0.0164464 +vt -0.1242836 -0.02133137 +vt -0.1016751 -0.02055635 +vt 0.1212808 0.1178688 +vt 0.1042064 0.12052 +vt 0.1016979 0.117381 +vt 0.1256555 0.0683793 +vt 0.1226187 0.0690648 +vt 0.1030349 0.06861331 +vt -0.06885661 -0.05426354 +vt -0.08479238 -0.06094307 +vt -0.0872846 -0.06280877 +vt -0.06427599 0.0429099 +vt -0.04557212 0.04992529 +vt -0.05010713 0.05394819 +vt -0.1186936 0.00875746 +vt -0.1111613 0.01484358 +vt -0.1148017 0.01969101 +vt 0.1133721 0.1177334 +vt 0.09872365 0.1281269 +vt 0.09655823 0.1224647 +vt -0.1210305 0.057626 +vt -0.121323 0.05951564 +vt -0.1272598 0.05359038 +vt -0.03441339 0.1511454 +vt -0.03898577 0.147165 +vt -0.03325248 0.1451954 +vt -0.1214571 0.02656759 +vt -0.1206144 0.02485112 +vt -0.1133698 0.03189421 +vt 0.05701512 -0.0197142 +vt 0.0656883 -0.0248974 +vt 0.07260296 -0.02759513 +vt -0.0905035 0.1244898 +vt -0.08244815 0.1268278 +vt -0.07161251 0.1309845 +vt 0.1098242 0.1199025 +vt 0.1079822 0.1205848 +vt 0.1073509 0.1169247 +vt -0.07463787 0.01149246 +vt -0.07068079 0.01041753 +vt -0.07288663 0.01238216 +vt -0.09607417 -0.01917072 +vt -0.09462651 -0.01839539 +vt -0.0896282 -0.007019095 +vt -0.08036827 0.006541973 +vt -0.07875977 0.01191062 +vt -0.08519926 0.00165505 +vt -0.03451624 0.1565128 +vt -0.03692249 0.1547995 +vt -0.03572354 0.1527238 +vt -0.06357461 -0.005884686 +vt -0.06871022 -0.01052367 +vt -0.07308383 -0.01582387 +vt 0.1154477 0.1019501 +vt 0.1264731 0.09787957 +vt 0.1167904 0.1041863 +vt -0.07543775 0.008823099 +vt -0.07690215 0.003413375 +vt -0.07148749 0.007723369 +vt 0.1106047 0.07933239 +vt 0.1089076 0.0787183 +vt 0.1182114 0.07186498 +vt 0.1143609 0.06150217 +vt 0.1159351 0.06103433 +vt 0.1070294 0.0692399 +vt -0.0856042 0.133481 +vt -0.08607899 0.1360457 +vt -0.08776338 0.1366938 +vt 0.1143609 0.06150217 +vt 0.1159351 0.06103433 +vt 0.1070294 0.0692399 +vt -0.00711296 -0.08452687 +vt -0.006638455 -0.07278366 +vt -0.009549673 -0.07189343 +vt 0.02740077 0.1438553 +vt 0.03042767 0.1435304 +vt 0.02961959 0.1471556 +vt -0.1268686 0.09593622 +vt -0.1149407 0.1007597 +vt -0.1164133 0.1026511 +vt -0.1132122 0.08596116 +vt -0.116635 0.07847779 +vt -0.1161803 0.07826982 +vt -0.1127575 0.08575319 +vt -0.09220141 0.1340483 +vt -0.0955446 0.1265291 +vt -0.09508772 0.1263259 +vt -0.09174453 0.1338452 +vt 0.1076475 0.09159471 +vt 0.1012101 0.09672074 +vt 0.1008986 0.0963296 +vt 0.107336 0.09120356 +vt 0.1088697 0.1063079 +vt 0.1060496 0.1140386 +vt 0.1055798 0.1138672 +vt 0.1083999 0.1061365 +vt 0.04792935 0.1433837 +vt 0.04180641 0.1488816 +vt 0.04147236 0.1485095 +vt 0.04759529 0.1430117 +vt 0.03883927 0.07155949 +vt 0.03899961 0.06333206 +vt 0.04687391 0.06094213 +vt 0.05158016 0.0676925 +vt 0.04661448 0.07425439 +vt -0.1090102 0.0720264 +vt -0.1118069 0.06428732 +vt -0.1082356 0.06319106 +vt -0.1053391 0.07089337 +vt -0.091727 0.1338432 +vt -0.09507115 0.1263244 +vt -0.09153756 0.1253288 +vt -0.0881338 0.1328208 +vt 0.1156497 0.06152174 +vt 0.1103468 0.06781421 +vt 0.1076163 0.06526272 +vt 0.1129695 0.059013 +vt 0.1053661 0.1045086 +vt 0.1025635 0.1122455 +vt 0.09884425 0.1112788 +vt 0.1017466 0.1035787 +vt 0.05987245 0.1339858 +vt 0.05440415 0.1401351 +vt 0.05130423 0.1378654 +vt 0.05677184 0.1317156 +vt -0.04742923 0.07296612 +vt -0.052449 0.06644552 +vt -0.04779873 0.05965646 +vt -0.03990494 0.06198118 +vt -0.03967657 0.07020701 +vt -0.1126079 0.08761457 +vt -0.1170812 0.07738168 +vt -0.116623 0.07718141 +vt -0.1121497 0.0874143 +vt -0.09040387 0.137115 +vt -0.09136991 0.1349397 +vt -0.09493659 0.1269083 +vt -0.09447964 0.1267054 +vt -0.08994691 0.1369121 +vt 0.1126091 0.0847635 +vt 0.1108731 0.08631867 +vt 0.1062476 0.09046261 +vt 0.104291 0.09221547 +vt 0.1039573 0.09184305 +vt 0.1122754 0.08439109 +vt 0.1087505 0.1067333 +vt 0.1050009 0.1172529 +vt 0.1045299 0.1170851 +vt 0.1082795 0.1065654 +vt 0.04346379 0.1439831 +vt 0.04188982 0.1453021 +vt 0.03631011 0.1499778 +vt 0.03490401 0.1511562 +vt 0.03458287 0.1507729 +vt 0.04314264 0.1435999 +vt 0.03859253 0.07477754 +vt 0.03847141 0.06361028 +vt 0.04096083 0.06277146 +vt 0.04090803 0.06440245 +vt 0.04062173 0.07324729 +vt 0.04055234 0.07539091 +vt -0.1115371 0.08516809 +vt -0.1158737 0.07487652 +vt -0.1081331 0.0712769 +vt -0.09044557 0.1366012 +vt -0.09494228 0.1263786 +vt -0.08806984 0.1235113 +vt 0.116101 0.07012583 +vt 0.1085203 0.07832673 +vt 0.1028228 0.07304142 +vt 0.1059965 0.1057554 +vt 0.1022465 0.1162749 +vt 0.09359471 0.1136038 +vt 0.04671876 0.1411094 +vt 0.03839004 0.1485495 +vt 0.04023906 0.1347846 +vt -0.0485208 0.06271283 +vt -0.05463352 0.05336634 +vt -0.04763341 0.04466456 +vt -0.03719438 0.04863306 +vt -0.03774281 0.05978751 +vt -0.1063763 0.06940357 +vt -0.1014021 0.07940255 +vt -0.1101423 0.08320101 +vt 0.02503191 0.1526401 +vt 0.01901537 0.1452495 +vt 0.02837731 0.1391605 +vt -0.08299437 0.1268454 +vt -0.07715007 0.1363621 +vt -0.08486463 0.140017 +vt 0.101952 0.07948565 +vt 0.1099468 0.07168785 +vt 0.1152929 0.07687157 +vt 0.09472505 0.1153084 +vt 0.09966098 0.1052905 +vt 0.1071245 0.1074564 +vt 0.05429678 0.06709726 +vt 0.0557166 0.06900758 +vt 0.04925064 0.07811327 +vt 0.04749985 0.07756533 +vt 0.04874702 0.07564453 +vt 0.05329662 0.06863762 +vt 0.04096082 0.06277189 +vt 0.04684603 0.06078886 +vt 0.04811948 0.0623384 +vt 0.04090803 0.06440288 +vt 0.04874703 0.07564454 +vt 0.04749986 0.07756533 +vt 0.04055234 0.07539096 +vt 0.04062172 0.07324734 +vt 0.04684583 0.06078869 +vt 0.04905448 0.06004447 +vt 0.05429659 0.06709749 +vt 0.05329644 0.06863783 +vt 0.04811928 0.06233824 +vt 0.06042005 0.1400615 +vt 0.05567345 0.1466915 +vt 0.05380367 0.1449886 +vt 0.1090244 0.06430319 +vt 0.1065122 0.07227091 +vt 0.1045174 0.07067419 +vt -0.01340094 0.1599647 +vt -0.02074384 0.1557398 +vt -0.01893001 0.1538718 +vt -0.1014335 0.1117869 +vt -0.1043637 0.1034366 +vt -0.1019694 0.1024032 +vt -0.09895404 0.1022399 +vt -0.1050736 0.09790201 +vt -0.09772499 0.09993985 +vt 0.06604317 0.1456356 +vt 0.07035102 0.1387124 +vt 0.07234798 0.1403156 +vt -0.1009395 0.1124417 +vt -0.09987329 0.1101134 +vt -0.09348343 0.1140423 +vt 0.1101286 0.08789753 +vt 0.1132709 0.08015662 +vt 0.1151113 0.08189123 +vt 0.002846764 0.1586051 +vt 0.01075032 0.1616549 +vt 0.009274639 0.1637408 +vt -0.09130703 0.1005571 +vt -0.08848813 0.1089456 +vt -0.09089162 0.1099469 +vt 0.05002477 0.06340988 +vt 0.05546781 0.06948122 +vt 0.05122348 0.07667714 +vt 0.04300268 0.07463127 +vt 0.04290868 0.0657823 +vt -0.006475641 0.1533231 +vt -0.0114986 0.1567173 +vt -0.01177854 0.156303 +vt -0.006755578 0.1529089 +vt -0.0747896 0.09971711 +vt -0.07908086 0.09543516 +vt -0.07872769 0.09508123 +vt -0.07443643 0.09936318 +vt 0.1166597 0.09208065 +vt 0.1108894 0.09393932 +vt 0.1107361 0.0934634 +vt 0.1165064 0.09160472 +vt 0.05920173 0.06907564 +vt 0.05513092 0.07356769 +vt 0.05805777 0.06968994 +vt -0.09210218 0.03360453 +vt -0.09663808 0.03762642 +vt -0.0978532 0.03168727 +vt -0.02150865 0.1506603 +vt -0.01634552 0.1474835 +vt -0.01356576 0.1653571 +vt 0.1074875 0.1151102 +vt 0.1133728 0.1136564 +vt 0.1132144 0.1322682 +vt -0.06883705 0.09612168 +vt -0.07584256 0.1132075 +vt -0.06999642 0.09553699 +vt -0.08491834 0.1101264 +vt -0.07818113 0.09293307 +vt -0.07504927 0.09664721 +o group2002521463 +g mesh2002521463 +usemtl mat10 +f 3/3/1 2/2/1 1/1/1 +f 3/6/2 1/5/2 4/4/2 +f 6/9/3 5/8/3 4/7/3 +f 4/12/4 2/11/4 3/10/4 +f 7/15/5 1/14/5 5/13/5 +f 7/18/6 4/17/6 1/16/6 +f 7/21/7 6/20/7 8/19/7 +f 4/24/8 8/23/8 6/22/8 +f 7/27/9 8/26/9 4/25/9 +f 5/30/10 6/29/10 7/28/10 +f 2/33/11 5/32/11 1/31/11 +f 4/36/12 5/35/12 2/34/12 +o group1119943561 +g mesh1119943561 +usemtl mat10 +f 12/40/13 11/39/13 10/38/13 9/37/13 +f 11/44/14 14/43/14 13/42/14 10/41/14 +f 14/48/15 12/47/15 9/46/15 13/45/15 +f 15/51/16 10/50/16 13/49/16 +f 14/54/17 11/53/17 12/52/17 +f 16/57/18 9/56/18 10/55/18 +f 16/60/19 13/59/19 9/58/19 +f 19/63/20 18/62/20 17/61/20 +f 13/67/21 17/66/21 18/65/21 15/64/21 +f 16/71/22 19/70/22 17/69/22 13/68/22 +f 10/74/23 15/73/23 16/72/23 +f 20/77/24 15/76/24 18/75/24 +f 20/80/25 21/79/25 16/78/25 +f 22/83/26 19/82/26 16/81/26 +f 22/86/27 18/85/27 19/84/27 +f 22/90/28 21/89/28 20/88/28 23/87/28 +f 18/93/29 23/92/29 20/91/29 +f 22/96/30 23/95/30 18/94/30 +f 16/99/31 21/98/31 22/97/31 +f 16/102/32 15/101/32 20/100/32 +o group634111619 +g mesh634111619 +usemtl mat10 +f 26/105/33 25/104/33 24/103/33 +f 25/108/34 28/107/34 27/106/34 +f 28/111/35 26/110/35 24/109/35 +f 24/114/36 27/113/36 29/112/36 +f 28/117/37 25/116/37 26/115/37 +f 28/120/38 29/119/38 27/118/38 +f 24/123/39 29/122/39 28/121/39 +f 25/126/40 27/125/40 24/124/40 +o group1265393450 +g mesh1265393450 +usemtl mat9 +f 33/130/41 32/129/41 31/128/41 30/127/41 +f 32/134/42 35/133/42 34/132/42 31/131/42 +f 35/138/43 33/137/43 30/136/43 34/135/43 +f 36/141/44 31/140/44 34/139/44 +f 35/144/45 32/143/45 33/142/45 +f 37/147/46 30/146/46 31/145/46 +f 37/150/47 34/149/47 30/148/47 +f 37/153/48 36/152/48 38/151/48 +f 34/156/49 38/155/49 36/154/49 +f 37/159/50 38/158/50 34/157/50 +f 31/162/51 36/161/51 37/160/51 +o group792360013 +g mesh792360013 +usemtl mat9 +f 41/165/52 40/164/52 39/163/52 +f 43/168/53 42/167/53 40/166/53 +f 43/172/54 41/171/54 39/170/54 42/169/54 +f 44/175/55 40/174/55 42/173/55 +f 43/178/56 40/177/56 41/176/56 +f 45/181/57 39/180/57 40/179/57 +f 42/184/58 46/183/58 44/182/58 +f 45/187/59 44/186/59 46/185/59 +f 46/190/60 42/189/60 39/188/60 +f 45/193/61 46/192/61 39/191/61 +f 40/196/62 44/195/62 45/194/62 +o group481990970 +g mesh481990970 +usemtl mat10 +f 49/199/63 48/198/63 47/197/63 +f 48/203/64 51/202/64 50/201/64 47/200/64 +f 51/207/65 49/206/65 52/205/65 50/204/65 +f 53/210/66 47/209/66 50/208/66 +f 51/213/67 48/212/67 49/211/67 +f 47/216/68 53/215/68 49/214/68 +f 52/219/69 53/218/69 50/217/69 +f 52/222/70 49/221/70 54/220/70 +f 52/225/71 54/224/71 53/223/71 +f 53/228/72 54/227/72 49/226/72 +o group1935184771 +g mesh1935184771 +usemtl mat9 +f 57/231/73 56/230/73 55/229/73 +f 56/234/74 59/233/74 58/232/74 +f 59/237/75 57/236/75 55/235/75 +f 55/240/76 58/239/76 60/238/76 +f 59/243/77 56/242/77 57/241/77 +f 59/246/78 60/245/78 58/244/78 +f 55/249/79 60/248/79 59/247/79 +f 56/252/80 58/251/80 55/250/80 +o group2043820846 +g mesh2043820846 +usemtl mat10 +f 63/255/81 62/254/81 61/253/81 +f 62/258/82 64/257/82 61/256/82 +f 64/261/83 66/260/83 65/259/83 +f 65/264/84 61/263/84 67/262/84 +f 68/267/85 64/266/85 62/265/85 +f 65/270/86 67/269/86 64/268/86 +f 69/273/87 66/272/87 63/271/87 +f 64/276/88 67/275/88 61/274/88 +f 66/279/89 69/278/89 61/277/89 +f 61/282/90 65/281/90 66/280/90 +f 61/285/91 69/284/91 63/283/91 +f 61/288/92 65/287/92 66/286/92 +f 68/291/93 63/290/93 66/289/93 +f 62/294/94 63/293/94 68/292/94 +f 64/297/95 68/296/95 66/295/95 +o group971385351 +g mesh971385351 +usemtl mat20 +f 73/301/96 72/300/96 71/299/96 70/298/96 +f 72/305/97 75/304/97 74/303/97 71/302/97 +f 75/309/98 77/308/98 76/307/98 74/306/98 +f 77/313/99 79/312/99 78/311/99 76/310/99 +f 79/317/100 73/316/100 70/315/100 78/314/100 +f 70/322/101 71/321/101 74/320/101 76/319/101 78/318/101 +f 81/326/102 80/325/102 72/324/102 73/323/102 +f 80/330/103 82/329/103 75/328/103 72/327/103 +f 82/334/104 83/333/104 77/332/104 75/331/104 +f 83/338/105 84/337/105 79/336/105 77/335/105 +f 84/342/106 81/341/106 73/340/106 79/339/106 +f 84/347/107 83/346/107 82/345/107 80/344/107 81/343/107 +o group7854860 +g mesh7854860 +usemtl mat21 +f 88/351/108 87/350/108 86/349/108 85/348/108 +f 87/356/109 91/355/109 90/354/109 89/353/109 86/352/109 +f 91/362/110 95/361/110 94/360/110 93/359/110 92/358/110 90/357/110 +f 95/366/111 97/365/111 96/364/111 94/363/111 +f 97/372/112 88/371/112 85/370/112 99/369/112 98/368/112 96/367/112 +f 98/378/113 101/377/113 100/376/113 93/375/113 94/374/113 96/373/113 +f 102/381/114 87/380/114 88/379/114 +f 103/384/115 91/383/115 87/382/115 +f 104/387/116 95/386/116 91/385/116 +f 105/390/117 97/389/117 95/388/117 +f 105/393/118 88/392/118 97/391/118 +f 105/398/119 104/397/119 103/396/119 102/395/119 106/394/119 +f 88/401/120 106/400/120 102/399/120 +f 105/404/121 106/403/121 88/402/121 +f 87/407/122 102/406/122 103/405/122 +f 91/410/123 103/409/123 104/408/123 +f 95/413/124 104/412/124 105/411/124 +f 108/419/125 107/418/125 99/417/125 85/416/125 86/415/125 89/414/125 +f 100/423/126 109/422/126 92/421/126 93/420/126 +f 101/427/127 98/426/127 99/425/127 107/424/127 +f 109/432/128 108/431/128 89/430/128 90/429/128 92/428/128 +f 110/435/129 108/434/129 109/433/129 +f 111/438/130 107/437/130 108/436/130 +f 112/441/131 101/440/131 107/439/131 +f 113/444/132 100/443/132 101/442/132 +f 113/447/133 109/446/133 100/445/133 +f 109/450/134 114/449/134 110/448/134 +f 113/453/135 114/452/135 109/451/135 +f 108/456/136 110/455/136 111/454/136 +f 107/459/137 111/458/137 112/457/137 +f 101/462/138 112/461/138 113/460/138 +f 113/467/139 112/466/139 111/465/139 110/464/139 114/463/139 +o group1104563999 +g mesh1104563999 +usemtl mat10 +f 118/471/140 117/470/140 116/469/140 115/468/140 +f 117/475/141 120/474/141 119/473/141 116/472/141 +f 120/479/142 118/478/142 115/477/142 119/476/142 +f 121/482/143 116/481/143 119/480/143 +f 120/485/144 117/484/144 118/483/144 +f 122/488/145 115/487/145 116/486/145 +f 122/491/146 119/490/146 115/489/146 +f 119/494/147 122/493/147 121/492/147 +f 116/497/148 121/496/148 122/495/148 diff --git a/processing/mode/libraries/ar/examples/Spheres/Spheres.pde b/processing/mode/libraries/ar/examples/Spheres/Spheres.pde new file mode 100644 index 000000000..f04b91f0e --- /dev/null +++ b/processing/mode/libraries/ar/examples/Spheres/Spheres.pde @@ -0,0 +1,62 @@ +import processing.ar.*; + +ARTracker tracker; +ARAnchor anchor; +PShape arObj; +float angle; + +void setup() { + fullScreen(AR); + + noStroke(); + + tracker = new ARTracker(this); + tracker.start(); +} + +void draw() { + lights(); + + if (mousePressed) { + // Create new anchor at the current touch point + if (anchor != null) anchor.dispose(); + ARTrackable hit = tracker.get(mouseX, mouseY); + if (hit != null) anchor = new ARAnchor(hit); + else anchor = null; + } + + if (anchor != null) { + anchor.attach(); + fill(217, 121, 255); + sphere(0.1); + rotateY(angle); + translate(0, 0, 0.3); + sphere(0.05); + angle += 0.1; + anchor.detach(); + } + + // Draw trackable planes + for (int i = 0; i < tracker.count(); i++) { + ARTrackable trackable = tracker.get(i); + if (!trackable.isTracking()) continue; + + pushMatrix(); + trackable.transform(); + if (mousePressed && trackable.isSelected(mouseX, mouseY)) { + fill(255, 0, 0, 100); + } else { + fill(255, 100); + } + + beginShape(QUADS); + float lx = trackable.lengthX(); + float lz = trackable.lengthZ(); + vertex(-lx/2, 0, -lz/2); + vertex(-lx/2, 0, +lz/2); + vertex(+lx/2, 0, +lz/2); + vertex(+lx/2, 0, -lz/2); + endShape(); + popMatrix(); + } +} diff --git a/processing/mode/libraries/ar/examples/Spheres/code/sketch.properties b/processing/mode/libraries/ar/examples/Spheres/code/sketch.properties new file mode 100644 index 000000000..7bae3ea6c --- /dev/null +++ b/processing/mode/libraries/ar/examples/Spheres/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=ar +mode.id=processing.mode.android.AndroidMode diff --git a/processing/mode/libraries/ar/library.properties b/processing/mode/libraries/ar/library.properties new file mode 100644 index 000000000..e5f844ae3 --- /dev/null +++ b/processing/mode/libraries/ar/library.properties @@ -0,0 +1,10 @@ +name = AR +authorList = The Processing Foundation, Syam Sundar K +url = https://android.processing.org +category = 3D +sentence = Renderer to develop AR apps +paragraph = +version = 13 +prettyVersion = 4.2.1 +minRevision = 249 +maxRevision = 1269 \ No newline at end of file diff --git a/processing/mode/libraries/vr/README.md b/processing/mode/libraries/vr/README.md new file mode 100644 index 000000000..e14303c16 --- /dev/null +++ b/processing/mode/libraries/vr/README.md @@ -0,0 +1,4 @@ +# VR library for Processing-Android + +This library includes a stereo renderer to create VR apps. + diff --git a/processing/mode/libraries/vr/build.gradle b/processing/mode/libraries/vr/build.gradle new file mode 100644 index 000000000..b223c4910 --- /dev/null +++ b/processing/mode/libraries/vr/build.gradle @@ -0,0 +1,132 @@ +import java.nio.file.Files +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING + +plugins { + id 'java-library' + id 'maven-publish' +} + +dependencies { + compileOnly name: "android" + compileOnly "org.p5android:processing-core:${modeVersion}" + +// commenting due to issue #718 +// implementationAar "com.google.vr:sdk-audio:${gvrVersion}" +// implementationAar "com.google.vr:sdk-base:${gvrVersion}" + +// fix for Issue #718 + implementation fileTree(dir: "../../../../libs/google-vr/", include: ["*.aar"]) +} + +sourceSets { + main { + java.srcDir("../../../../libs/processing-vr/src/main/java/") + resources { + srcDir("../../../../libs/processing-vr/src/main/") + exclude "AndroidManifest.xml" + exclude "**/java/**" + } + } +} + +java { + withSourcesJar() +} + +tasks.named('jar') { + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + +tasks.register("sourceJar", Jar) { + from sourceSets.main.allJava + archiveClassifier.set("sources") +} + +// Does not work because of Processing-specific tags in source code, such as @webref +tasks.register("javadocJar", Jar) { + dependsOn javadoc + archiveClassifier.set("javadoc") + from javadoc.destinationDir +} + +artifacts { +// archives javadocJar + archives sourceJar +} + +tasks.jar { + doLast { + ant.checksum file: archiveFile.get().asFile + } +} + +tasks.clean { + doFirst { + delete "dist" + delete "library/vr.jar" + } +} + +tasks.compileJava { + doFirst { + String[] deps = ["sdk-audio.jar", + "sdk-base.jar", + "sdk-common.jar"] + File libFolder = file("library") + libFolder.mkdirs() + deps.each { String fn -> + Files.copy( + file("${rootDir}/build/libs/" + fn).toPath(), + file("library/" + fn).toPath(), + REPLACE_EXISTING + ) + } + } +} + +tasks.build { + doLast { + // Copying vr jar to library folder + File vrJar = file("library/vr.jar") + vrJar.mkdirs() + + // Need to check the existance of the files before using as the files + // will get generated only if Task ':mode:libraries:vr:jar' is not being skipped + // Task ':mode:libraries:vr:jar' will be skipped if source files are unchanged or jar task is UP-TO-DATE + def vrJarFile = file("$buildDir/libs/vr.jar") + if (vrJarFile.exists()) { + Files.copy(vrJarFile.toPath(), vrJar.toPath(), REPLACE_EXISTING) + } + + // Renaming artifacts for maven publishing + def processingVrJar = file("$buildDir/libs/processing-vr-${vrLibVersion}.jar") + if (vrJarFile.exists()) { + Files.move(vrJarFile.toPath(), processingVrJar.toPath(), REPLACE_EXISTING) + } + + def processingVrSourcesJar = file("$buildDir/libs/processing-vr-${vrLibVersion}-sources.jar") + def vrSourcesJar = file("$buildDir/libs/vr-sources.jar") + if (vrSourcesJar.exists()) { + Files.move(vrSourcesJar.toPath(), processingVrSourcesJar.toPath(), REPLACE_EXISTING) + } + + def vrMd5File = file("$buildDir/libs/vr.jar.MD5") + def processingVrMd5File = file("$buildDir/libs/processing-vr-${vrLibVersion}.jar.md5") + if (vrMd5File.exists()) { + Files.move(vrMd5File.toPath(), processingVrMd5File.toPath(), REPLACE_EXISTING) + } + } +} + +ext { + libName = 'processing-vr' + libVersion = vrLibVersion + libJar = "${buildDir}/libs/${libName}-${libVersion}.jar" + libSrc = "${buildDir}/libs/${libName}-${libVersion}-sources.jar" + libMd5 = "${buildDir}/libs/${libName}-${libVersion}-sources.jar.md5" + libDependencies = [[group: 'org.p5android', name: 'processing-core', version: modeVersion], + [group: 'com.google.vr', name: 'sdk-base', version: gvrVersion], + [group: 'com.google.vr', name: 'sdk-audio', version: gvrVersion]] +} + +apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" diff --git a/processing/mode/libraries/vr/examples/Cube/Cube.pde b/processing/mode/libraries/vr/examples/Cube/Cube.pde new file mode 100644 index 000000000..7ecd2cb78 --- /dev/null +++ b/processing/mode/libraries/vr/examples/Cube/Cube.pde @@ -0,0 +1,14 @@ +import processing.vr.*; + +void setup() { + fullScreen(VR); +} + +void draw() { + background(157); + lights(); + translate(width/2, height/2); + rotateX(frameCount * 0.01); + rotateY(frameCount * 0.01); + box(350); +} diff --git a/processing/mode/libraries/vr/examples/Cube/code/sketch.properties b/processing/mode/libraries/vr/examples/Cube/code/sketch.properties new file mode 100644 index 000000000..8b38b89c7 --- /dev/null +++ b/processing/mode/libraries/vr/examples/Cube/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/GenerateRay/GenerateRay.pde b/processing/mode/libraries/vr/examples/GenerateRay/GenerateRay.pde new file mode 100644 index 000000000..e9fcac44d --- /dev/null +++ b/processing/mode/libraries/vr/examples/GenerateRay/GenerateRay.pde @@ -0,0 +1,59 @@ +import processing.vr.*; + +PVector origin, direction; +float[] randomx = new float[5]; +float[] randomy = new float[5]; +float[] randomz = new float[5]; +VRCamera cam; + +void setup() { + fullScreen(VR); + cameraUp(); + cam = new VRCamera(this); + + for (int i = 0; i < 5; ++i) { + randomx[i] = random(-500, 500); + randomy[i] = random(-500, 500); + randomz[i] = random(-100, 100); + } + noStroke(); + + origin = new PVector(randomx[0], randomy[0], randomz[0]); + direction = new PVector(); +} + +void draw() { + background(200, 0, 150); + lights(); + + cam.setPosition(0, 0, 400); + push(); + translate(randomx[0], randomy[0], randomz[0]); + fill(0, 255, 0); + sphere(30); + + int r = floor(random(1, 5)); + float rx = randomx[r] - randomx[0]; + float ry = randomy[r] - randomy[0]; + float rz = randomz[r] - randomz[0]; + stroke(0); + strokeWeight(1 * displayDensity); + line(0, 0, 0, rx, ry, rz); + noStroke(); + + direction.set(rx, ry, rz); + direction.normalize(); + + pop(); + + for (int i = 1; i < 5; ++i) { + push(); + translate(randomx[i], randomy[i], randomz[i]); + fill(255, 0, 0); + if (intersectsSphere(70, origin, direction)) { + fill(0, 0, 255); + } + sphere(70); + pop(); + } +} \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/GenerateRay/code/sketch.properties b/processing/mode/libraries/vr/examples/GenerateRay/code/sketch.properties new file mode 100644 index 000000000..0828c1ecf --- /dev/null +++ b/processing/mode/libraries/vr/examples/GenerateRay/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode diff --git a/processing/mode/libraries/vr/examples/IntersectsBox/IntersectsBox.pde b/processing/mode/libraries/vr/examples/IntersectsBox/IntersectsBox.pde new file mode 100644 index 000000000..566cb7a72 --- /dev/null +++ b/processing/mode/libraries/vr/examples/IntersectsBox/IntersectsBox.pde @@ -0,0 +1,27 @@ +import processing.vr.*; + +VRCamera cam; +float rotSpeed = 0.3; +float rotAngle = 0; + +void setup() { + fullScreen(VR); + cameraUp(); + cam = new VRCamera(this); +} + +void draw() { + background(200, 0, 150); + + cam.setPosition(0, 0, 200); + push(); + rotateZ(radians(rotAngle)); + translate(100, 0, 0); + fill(255, 0, 0); + if (intersectsBox(50, 0, 0)) { + rotAngle += rotSpeed; + fill(0, 0, 255); + } + box(50); + pop(); +} \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/IntersectsBox/code/sketch.properties b/processing/mode/libraries/vr/examples/IntersectsBox/code/sketch.properties new file mode 100644 index 000000000..0828c1ecf --- /dev/null +++ b/processing/mode/libraries/vr/examples/IntersectsBox/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode diff --git a/processing/mode/libraries/vr/examples/IntersectsPlane/IntersectsPlane.pde b/processing/mode/libraries/vr/examples/IntersectsPlane/IntersectsPlane.pde new file mode 100644 index 000000000..a1a6f2701 --- /dev/null +++ b/processing/mode/libraries/vr/examples/IntersectsPlane/IntersectsPlane.pde @@ -0,0 +1,39 @@ +import processing.vr.*; + +VRCamera cam; +float x, y; + +void setup() { + fullScreen(VR); + cameraUp(); + cam = new VRCamera(this); +} + +void draw() { + background(200, 0, 150); + + cam.setPosition(0, 0, 400); + fill(255, 170, 238); + plane(400, 400); + fill(255, 0, 0); + translate(x, y, 0); + PVector offset; + if (intersectsBox(60, 0, 0)) { + translate(-x, -y, 0); + fill(0, 0, 255); + offset = intersectsPlane(0, 0); + x = offset.x; + y = offset.y; + } + translate(x, y, 0); + box(60); +} + +void plane(float w, float d) { + beginShape(QUADS); + vertex(-w/2, -d/2); + vertex(+w/2, -d/2); + vertex(+w/2, +d/2); + vertex(-w/2, +d/2); + endShape(); +} \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/IntersectsPlane/code/sketch.properties b/processing/mode/libraries/vr/examples/IntersectsPlane/code/sketch.properties new file mode 100644 index 000000000..0828c1ecf --- /dev/null +++ b/processing/mode/libraries/vr/examples/IntersectsPlane/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode diff --git a/processing/mode/libraries/vr/examples/IntersectsSphere/IntersectsSphere.pde b/processing/mode/libraries/vr/examples/IntersectsSphere/IntersectsSphere.pde new file mode 100644 index 000000000..041b91ed4 --- /dev/null +++ b/processing/mode/libraries/vr/examples/IntersectsSphere/IntersectsSphere.pde @@ -0,0 +1,42 @@ +import processing.vr.*; + +float[] randomx = new float[5]; +float[] randomy = new float[5]; +VRCamera cam; + +void setup() { + fullScreen(VR); + cameraUp(); + cam = new VRCamera(this); + + for (int i = 0; i < 5; ++i) { + randomx[i] = random(-500, 500); + randomy[i] = random(-500, 500); + } +} + +void draw() { + background(200, 0, 150); + + lights(); + noStroke(); + cam.setPosition(0, 0, 400); + for (int i = 0; i < 5; ++i) { + push(); + translate(randomx[i], randomy[i]); + fill(255, 0, 0); + if (intersectsSphere(70, 0, 0)) { + fill(0, 0, 255); + } + sphere(70); + pop(); + } + + cam.sticky(); + strokeWeight(5 * displayDensity); + stroke(0, 0, 255); + noFill(); + translate(0, 0, 200); + circle(0, 0, 50); + cam.noSticky(); +} \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/IntersectsSphere/code/sketch.properties b/processing/mode/libraries/vr/examples/IntersectsSphere/code/sketch.properties new file mode 100644 index 000000000..0828c1ecf --- /dev/null +++ b/processing/mode/libraries/vr/examples/IntersectsSphere/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode diff --git a/processing/mode/libraries/vr/examples/Mono/Mono.pde b/processing/mode/libraries/vr/examples/Mono/Mono.pde new file mode 100644 index 000000000..df4714544 --- /dev/null +++ b/processing/mode/libraries/vr/examples/Mono/Mono.pde @@ -0,0 +1,14 @@ +import processing.vr.*; + +void setup() { + fullScreen(MONO); +} + +void draw() { + background(157); + lights(); + translate(width/2, height/2); + rotateX(frameCount * 0.01f); + rotateY(frameCount * 0.01f); + box(500); +} \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/Mono/code/sketch.properties b/processing/mode/libraries/vr/examples/Mono/code/sketch.properties new file mode 100644 index 000000000..8b38b89c7 --- /dev/null +++ b/processing/mode/libraries/vr/examples/Mono/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/Stereoscopy/Stereoscopy.pde b/processing/mode/libraries/vr/examples/Stereoscopy/Stereoscopy.pde new file mode 100644 index 000000000..8b3f12511 --- /dev/null +++ b/processing/mode/libraries/vr/examples/Stereoscopy/Stereoscopy.pde @@ -0,0 +1,88 @@ +// Stereoscopy in VR: +// https://github.com/processing/processing-android/issues/593 +// By Javier Marco Rubio (https://github.com/jmarco2000) + +import processing.vr.*; + +int eyedist = 50; + +VRCamera cam; + +float angulo=0; + +float cx, cz; +float px, pz; +float ang; +float orbita = 400; +float vorbita = 0.01; + +// eye matrix +PMatrix3D eyeMat = new PMatrix3D(); + +boolean ciclo = false; //test witch eye is beign draw +float orientacion; + +void setup() { + fullScreen(VR); + cameraUp(); + rectMode(CENTER); + cam = new VRCamera(this); + cam.setNear(10); + cam.setFar(2500); + + //posicion planeta + //centro + cx=width/2; + cz=-200; + ang=0; +} + +void draw() { + ciclo = !ciclo; + + background(0); + lights(); + if (!ciclo) { + getEyeMatrix(eyeMat); + orientacion = acos(eyeMat.m00); + if (eyeMat.m02 < 0) orientacion =- orientacion; + println(degrees(orientacion)); + } + if (ciclo) { + cam.setPosition(+(eyedist/2) * cos(orientacion), 100, 500 + (eyedist/2) * sin(orientacion)); + } else { + cam.setPosition(-(eyedist/2) * cos(orientacion), 100, 500 - (eyedist/2) * sin(orientacion)); + } + + pushMatrix(); + px = cx + cos(ang) * orbita; + pz = cz + sin(ang) * orbita; + translate(px, 100, pz); + ang = ang + vorbita; + rotateY(1.25); + rotateX(-0.4); + rotateZ(angulo); //angulo=angulo+0.01; + noStroke(); + fill(251, 100, 10); + box(100); + popMatrix(); + + pushMatrix(); + translate(width/2, 100, cz); + rotateY(1.25); + rotateX(-0.4); + rotateZ(angulo); //angulo=angulo+0.01; + noStroke(); + fill(0, 200, 10); + sphere(70); + popMatrix(); +} + +void mousePressed() { + vorbita = 0; +} + +void mouseReleased() { + vorbita = 0.01; +} + diff --git a/processing/mode/libraries/vr/examples/Stereoscopy/code/sketch.properties b/processing/mode/libraries/vr/examples/Stereoscopy/code/sketch.properties new file mode 100644 index 000000000..8b38b89c7 --- /dev/null +++ b/processing/mode/libraries/vr/examples/Stereoscopy/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode \ No newline at end of file diff --git a/processing/mode/libraries/vr/examples/drawAim/code/sketch.properties b/processing/mode/libraries/vr/examples/drawAim/code/sketch.properties new file mode 100644 index 000000000..0828c1ecf --- /dev/null +++ b/processing/mode/libraries/vr/examples/drawAim/code/sketch.properties @@ -0,0 +1,3 @@ +mode=Android +component=vr +mode.id=processing.mode.android.AndroidMode diff --git a/processing/mode/libraries/vr/examples/drawAim/drawAim.pde b/processing/mode/libraries/vr/examples/drawAim/drawAim.pde new file mode 100644 index 000000000..eaae4e797 --- /dev/null +++ b/processing/mode/libraries/vr/examples/drawAim/drawAim.pde @@ -0,0 +1,68 @@ +import processing.vr.*; + +VRCamera cam; + +void setup() { + fullScreen(VR); + cam = new VRCamera(this); +} + +void calculate() { + println("in calculate function"); +} + +void draw() { + background(150); + translate(width/2, height/2); + noStroke(); + + // Some lights + pointLight(255, 255, 255, 0, 0, 500); + + // Floor + beginShape(QUADS); + fill(255, 0, 0); + normal(0, 0, -1); + vertex(-width/2, +500, -width/2); + fill(0, 0, 255); + vertex(-width/2, +500, +width/2); + vertex(+width/2, +500, +width/2); + fill(255, 0, 0); + vertex(+width/2, +500, -width/2); + endShape(); + + // Large box at the center + pushMatrix(); + rotateY(millis()/1000.0); + fill(220); + box(200); + popMatrix(); + + // Red box, X axis + pushMatrix(); + translate(200, 0, 0); + fill(255, 0, 0); + box(100); + popMatrix(); + + // Green box, Y axis + pushMatrix(); + translate(0, 200, 0); + fill(0, 255, 0); + box(100); + popMatrix(); + + // Blue box, Z axis + pushMatrix(); + translate(0, 0, 200); + fill(0, 0, 255); + box(100); + popMatrix(); + + // Use eye coordinates at 100 units from the camera position:; + cam.sticky(); + stroke(255, 200); + strokeWeight(50); + point(0, 0, 100); + cam.noSticky(); +} \ No newline at end of file diff --git a/processing/mode/libraries/vr/library.properties b/processing/mode/libraries/vr/library.properties new file mode 100644 index 000000000..7c92c2cff --- /dev/null +++ b/processing/mode/libraries/vr/library.properties @@ -0,0 +1,10 @@ +name = VR +authorList = Processing Foundation +url = https://android.processing.org +category = 3D +sentence = Renderer to develop VR apps +paragraph = +version = 13 +prettyVersion = 4.2.1 +minRevision = 249 +maxRevision = 1269 \ No newline at end of file diff --git a/processing/mode/mode.properties b/processing/mode/mode.properties new file mode 100644 index 000000000..a03cb0ece --- /dev/null +++ b/processing/mode/mode.properties @@ -0,0 +1,10 @@ +name = Android Mode for Processing 4 +authorList = [The Processing Foundation](https://processingfoundation.org/) +url = https://android.processing.org +sentence = This mode lets you use Processing to create Android apps +paragraph = +imports=processing.mode.java.JavaMode +version = 412 +prettyVersion = 4.6.0 +minRevision = 1283 +maxRevision = 0 \ No newline at end of file diff --git a/processing/mode/mode/JavaMode.jar b/processing/mode/mode/JavaMode.jar new file mode 100644 index 000000000..04c55cc15 Binary files /dev/null and b/processing/mode/mode/JavaMode.jar differ diff --git a/processing/mode/mode/core.jar b/processing/mode/mode/core.jar new file mode 100644 index 000000000..e7b82a144 Binary files /dev/null and b/processing/mode/mode/core.jar differ diff --git a/processing/mode/mode/gradlew.zip b/processing/mode/mode/gradlew.zip new file mode 100644 index 000000000..5f7697c8a Binary files /dev/null and b/processing/mode/mode/gradlew.zip differ diff --git a/processing/mode/mode/istack-commons-runtime.jar b/processing/mode/mode/istack-commons-runtime.jar new file mode 100644 index 000000000..b91ea56ba Binary files /dev/null and b/processing/mode/mode/istack-commons-runtime.jar differ diff --git a/processing/mode/mode/javax.activation-api.jar b/processing/mode/mode/javax.activation-api.jar new file mode 100644 index 000000000..986c36509 Binary files /dev/null and b/processing/mode/mode/javax.activation-api.jar differ diff --git a/processing/mode/mode/jaxb-api.jar b/processing/mode/mode/jaxb-api.jar new file mode 100644 index 000000000..456586547 Binary files /dev/null and b/processing/mode/mode/jaxb-api.jar differ diff --git a/processing/mode/mode/jaxb-jxc.jar b/processing/mode/mode/jaxb-jxc.jar new file mode 100644 index 000000000..039e6cb2d Binary files /dev/null and b/processing/mode/mode/jaxb-jxc.jar differ diff --git a/processing/mode/mode/jaxb-runtime.jar b/processing/mode/mode/jaxb-runtime.jar new file mode 100644 index 000000000..0b9ef67c4 Binary files /dev/null and b/processing/mode/mode/jaxb-runtime.jar differ diff --git a/processing/mode/mode/jaxb-xjc.jar b/processing/mode/mode/jaxb-xjc.jar new file mode 100644 index 000000000..aab6be555 Binary files /dev/null and b/processing/mode/mode/jaxb-xjc.jar differ diff --git a/processing/mode/mode/jdi.jar b/processing/mode/mode/jdi.jar new file mode 100644 index 000000000..397e9e4da Binary files /dev/null and b/processing/mode/mode/jdi.jar differ diff --git a/processing/mode/mode/jdimodel.jar b/processing/mode/mode/jdimodel.jar new file mode 100644 index 000000000..3e272ce72 Binary files /dev/null and b/processing/mode/mode/jdimodel.jar differ diff --git a/processing/mode/mode/org.eclipse.core.contenttype.jar b/processing/mode/mode/org.eclipse.core.contenttype.jar new file mode 100644 index 000000000..2d8f772d1 Binary files /dev/null and b/processing/mode/mode/org.eclipse.core.contenttype.jar differ diff --git a/processing/mode/mode/org.eclipse.core.jobs.jar b/processing/mode/mode/org.eclipse.core.jobs.jar new file mode 100644 index 000000000..579555f49 Binary files /dev/null and b/processing/mode/mode/org.eclipse.core.jobs.jar differ diff --git a/processing/mode/mode/org.eclipse.core.resources.jar b/processing/mode/mode/org.eclipse.core.resources.jar new file mode 100644 index 000000000..dd04e3eef Binary files /dev/null and b/processing/mode/mode/org.eclipse.core.resources.jar differ diff --git a/processing/mode/mode/org.eclipse.core.runtime.jar b/processing/mode/mode/org.eclipse.core.runtime.jar new file mode 100644 index 000000000..4b46a1262 Binary files /dev/null and b/processing/mode/mode/org.eclipse.core.runtime.jar differ diff --git a/processing/mode/mode/org.eclipse.equinox.common.jar b/processing/mode/mode/org.eclipse.equinox.common.jar new file mode 100644 index 000000000..c2a745e12 Binary files /dev/null and b/processing/mode/mode/org.eclipse.equinox.common.jar differ diff --git a/processing/mode/mode/org.eclipse.equinox.preferences.jar b/processing/mode/mode/org.eclipse.equinox.preferences.jar new file mode 100644 index 000000000..d6cb801c6 Binary files /dev/null and b/processing/mode/mode/org.eclipse.equinox.preferences.jar differ diff --git a/processing/mode/mode/org.eclipse.jdt.core.jar b/processing/mode/mode/org.eclipse.jdt.core.jar new file mode 100644 index 000000000..2b675bc84 Binary files /dev/null and b/processing/mode/mode/org.eclipse.jdt.core.jar differ diff --git a/processing/mode/mode/org.eclipse.osgi.jar b/processing/mode/mode/org.eclipse.osgi.jar new file mode 100644 index 000000000..91f36da60 Binary files /dev/null and b/processing/mode/mode/org.eclipse.osgi.jar differ diff --git a/processing/mode/mode/org.eclipse.text.jar b/processing/mode/mode/org.eclipse.text.jar new file mode 100644 index 000000000..e1263c239 Binary files /dev/null and b/processing/mode/mode/org.eclipse.text.jar differ diff --git a/processing/mode/mode/pde.jar b/processing/mode/mode/pde.jar new file mode 100644 index 000000000..0eba179a6 Binary files /dev/null and b/processing/mode/mode/pde.jar differ diff --git a/processing/mode/resources/device-art-resources/device-art.xml b/processing/mode/resources/device-art-resources/device-art.xml new file mode 100644 index 000000000..b67fa2033 --- /dev/null +++ b/processing/mode/resources/device-art-resources/device-art.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/processing/mode/resources/device-art-resources/pixel_3/layout b/processing/mode/resources/device-art-resources/pixel_3/layout new file mode 100644 index 000000000..4615fb58d --- /dev/null +++ b/processing/mode/resources/device-art-resources/pixel_3/layout @@ -0,0 +1,38 @@ +parts { + device { + display { + width 1080 + height 2160 + x 0 + y 0 + } + } + portrait { + background { + image port_back.webp + } + foreground { + mask round_corners.webp + } + onion { + image port_fore.webp + } + } +} +layouts { + portrait { + width 1194 + height 2532 + event EV_SW:0:1 + part1 { + name portrait + x 0 + y 0 + } + part2 { + name device + x 54 + y 196 + } + } +} diff --git a/processing/mode/resources/device-art-resources/pixel_3/port_back.webp b/processing/mode/resources/device-art-resources/pixel_3/port_back.webp new file mode 100644 index 000000000..6037b7963 Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_3/port_back.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_3/round_corners.webp b/processing/mode/resources/device-art-resources/pixel_3/round_corners.webp new file mode 100644 index 000000000..9dad9033a Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_3/round_corners.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_6/back.webp b/processing/mode/resources/device-art-resources/pixel_6/back.webp new file mode 100644 index 000000000..a41256802 Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_6/back.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_6/layout b/processing/mode/resources/device-art-resources/pixel_6/layout new file mode 100644 index 000000000..ae638d344 --- /dev/null +++ b/processing/mode/resources/device-art-resources/pixel_6/layout @@ -0,0 +1,36 @@ +parts { + device { + display { + width 1080 + height 2400 + x 0 + y 0 + } + } + portrait { + background { + image back.webp + } + foreground { + mask mask.webp + cutout hole + } + } +} +layouts { + portrait { + width 1209 + height 2553 + event EV_SW:0:1 + part1 { + name portrait + x 0 + y 0 + } + part2 { + name device + x 60 + y 69 + } + } +} diff --git a/processing/mode/resources/device-art-resources/pixel_6/mask.webp b/processing/mode/resources/device-art-resources/pixel_6/mask.webp new file mode 100644 index 000000000..c658b192d Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_6/mask.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_c/land_back.webp b/processing/mode/resources/device-art-resources/pixel_c/land_back.webp new file mode 100644 index 000000000..4cec52875 Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_c/land_back.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_c/land_fore.webp b/processing/mode/resources/device-art-resources/pixel_c/land_fore.webp new file mode 100644 index 000000000..66a0b9df0 Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_c/land_fore.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_c/land_shadow.webp b/processing/mode/resources/device-art-resources/pixel_c/land_shadow.webp new file mode 100644 index 000000000..15acd8679 Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_c/land_shadow.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_c/layout b/processing/mode/resources/device-art-resources/pixel_c/layout new file mode 100644 index 000000000..7378ba8c5 --- /dev/null +++ b/processing/mode/resources/device-art-resources/pixel_c/layout @@ -0,0 +1,59 @@ +parts { + device { + display { + width 1800 + height 2560 + x 0 + y 0 + } + } + portrait { + background { + image port_back.webp + } + onion { + image port_fore.webp + } + } + landscape { + background { + image land_back.webp + } + onion { + image land_fore.webp + } + } +} +layouts { + portrait { + width 2307 + height 2971 + event EV_SW:0:1 + part1 { + name portrait + x 0 + y 0 + } + part2 { + name device + x 259 + y 181 + } + } + landscape { + width 3096 + height 2215 + event EV_SW:0:0 + part1 { + name landscape + x 0 + y 0 + } + part2 { + name device + x 269 + y 1988 + rotation 3 + } + } +} diff --git a/processing/mode/resources/device-art-resources/pixel_c/port_back.webp b/processing/mode/resources/device-art-resources/pixel_c/port_back.webp new file mode 100644 index 000000000..ee9e3eaea Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_c/port_back.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_c/port_fore.webp b/processing/mode/resources/device-art-resources/pixel_c/port_fore.webp new file mode 100644 index 000000000..f2e6b7ca5 Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_c/port_fore.webp differ diff --git a/processing/mode/resources/device-art-resources/pixel_c/port_shadow.webp b/processing/mode/resources/device-art-resources/pixel_c/port_shadow.webp new file mode 100644 index 000000000..e7c1e74bf Binary files /dev/null and b/processing/mode/resources/device-art-resources/pixel_c/port_shadow.webp differ diff --git a/processing/mode/resources/device-art-resources/wearos_small_round/device_bezel.png b/processing/mode/resources/device-art-resources/wearos_small_round/device_bezel.png new file mode 100644 index 000000000..5eccf08ce Binary files /dev/null and b/processing/mode/resources/device-art-resources/wearos_small_round/device_bezel.png differ diff --git a/processing/mode/resources/device-art-resources/wearos_small_round/hardware.ini b/processing/mode/resources/device-art-resources/wearos_small_round/hardware.ini new file mode 100644 index 000000000..3f8ab78b8 --- /dev/null +++ b/processing/mode/resources/device-art-resources/wearos_small_round/hardware.ini @@ -0,0 +1,3 @@ +# skin-specific hardware values +hw.rotaryInput=yes +hw.sensors.heart_rate=yes diff --git a/processing/mode/resources/device-art-resources/wearos_small_round/layout b/processing/mode/resources/device-art-resources/wearos_small_round/layout new file mode 100644 index 000000000..6c45bac29 --- /dev/null +++ b/processing/mode/resources/device-art-resources/wearos_small_round/layout @@ -0,0 +1,46 @@ +parts { + portrait { + background { + image device_bezel.png + } + } + + device { + display { + width 384 + height 384 + x 0 + y 0 + } + } +} + +layouts { + portrait { + width 456 + height 456 + color 0x1f1f1f + event EV_SW:0:1 + + part1 { + name portrait + x 0 + y 0 + } + + part2 { + name device + x 36 + y 36 + } + } +} + +keyboard { + charmap qwerty2 +} + +network { + speed full + delay none +} diff --git a/processing/mode/resources/device-art-resources/wearos_square/device_bezel.png b/processing/mode/resources/device-art-resources/wearos_square/device_bezel.png new file mode 100644 index 000000000..01c5cc146 Binary files /dev/null and b/processing/mode/resources/device-art-resources/wearos_square/device_bezel.png differ diff --git a/processing/mode/resources/device-art-resources/wearos_square/hardware.ini b/processing/mode/resources/device-art-resources/wearos_square/hardware.ini new file mode 100644 index 000000000..3f8ab78b8 --- /dev/null +++ b/processing/mode/resources/device-art-resources/wearos_square/hardware.ini @@ -0,0 +1,3 @@ +# skin-specific hardware values +hw.rotaryInput=yes +hw.sensors.heart_rate=yes diff --git a/processing/mode/resources/device-art-resources/wearos_square/layout b/processing/mode/resources/device-art-resources/wearos_square/layout new file mode 100644 index 000000000..d1f9278d1 --- /dev/null +++ b/processing/mode/resources/device-art-resources/wearos_square/layout @@ -0,0 +1,46 @@ +parts { + portrait { + background { + image device_bezel.png + } + } + + device { + display { + width 360 + height 360 + x 0 + y 0 + } + } +} + +layouts { + portrait { + width 432 + height 432 + color 0x1f1f1f + event EV_SW:0:1 + + part1 { + name portrait + x 18 + y 18 + } + + part2 { + name device + x 36 + y 36 + } + } +} + +keyboard { + charmap qwerty2 +} + +network { + speed full + delay none +} diff --git a/processing/mode/scripts/permissions.py b/processing/mode/scripts/permissions.py new file mode 100644 index 000000000..4e8053864 --- /dev/null +++ b/processing/mode/scripts/permissions.py @@ -0,0 +1,84 @@ +import sys, re + +from urllib.request import urlopen +from bs4 import BeautifulSoup + +def get_soup(url): + page = urlopen(url) + soup = BeautifulSoup(page, features="lxml") + return soup + +def parse_all(soup): + print('Parsing all permissions...') + table = soup.find('table', { 'id': 'constants', 'class' : 'responsive constants' }) + entries = table.find_all('tr') + str_list = '' + for entry in entries: + if not entry or not entry.attrs: continue + info = entry.find('td', {'width':'100%'}) + if info: + name = info.find('code').find('a').contents[0] + pieces = [] + deprecated = False + for piece in info.find('p').contents: + piece_str = re.sub('\s+', ' ', str(piece)).strip() + if '' in piece_str: + piece_str = piece.find('a').contents[0].strip() + if '' in piece_str and 'This constant was deprecated' in piece_str: + deprecated = True + pieces += [piece_str] + if name and pieces and not deprecated: + desc = ' '.join(pieces).strip().replace('"', '\\"') + str_list += (',' if str_list else '') + '\n "' + name + '", "' + desc + '"' + str_list = 'static final String[] listing = {' + str_list + '\n };\n' + return str_list + +def replace_all(source, str_list): + print('Replacing old permissions...') + idx0 = source.find('static final String[] listing = {') + idx1 = source[idx0:].find(' };') + return source[:idx0] + str_list + source[idx0+idx1+5:] + +def parse_danger(soup): + print('Parsing dangerous permissions...') + entries = soup.find_all(lambda tag:tag.name == "div" and + len(tag.attrs) == 1 and + "data-version-added" in tag.attrs) + str_list = '' + for entry in entries: + name = entry.find('h3').contents[0] + items = entry.find_all('p') + for item in items: + text = item.getText().strip() + if 'Protection level:' in text and 'dangerous' in text: + str_list += (',' if str_list else '') + '\n "' + name + '"' + str_list = 'static final String[] dangerous = {' + str_list + '\n };\n' + return str_list + +def replace_danger(source, str_list): + print('Replacing dangerous permissions...') + idx0 = source.find('static final String[] dangerous = {') + idx1 = source[idx0:].find(' };') + return source[:idx0] + str_list + source[idx0+idx1+5:] + +java_file = '../src/processing/mode/android/Permissions.java' +ref_url = 'https://developer.android.com/reference/android/Manifest.permission.html' + +print('Reading Android reference...') +soup = get_soup(ref_url) + +print('Reading Permissions.java...') +with open(java_file, 'r') as f: + source = f.read() + +all_list = parse_all(soup) +source = replace_all(source, all_list) + +danger_list = parse_danger(soup) +source = replace_danger(source, danger_list) + +print('Writing Permissions.java...') +with open(java_file, 'w') as f: + f.write(source) + +print('Done.') \ No newline at end of file diff --git a/processing/mode/scripts/requirements.txt b/processing/mode/scripts/requirements.txt new file mode 100644 index 000000000..6f83e94d2 --- /dev/null +++ b/processing/mode/scripts/requirements.txt @@ -0,0 +1,2 @@ +beautifulsoup4 +lxml diff --git a/processing/mode/src/processing/mode/android/AVD.java b/processing/mode/src/processing/mode/android/AVD.java new file mode 100644 index 000000000..7674bc6b6 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AVD.java @@ -0,0 +1,482 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2013-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import processing.app.Base; +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.exec.StreamPump; +import processing.core.PApplet; + +import java.awt.Frame; +import java.io.*; +import java.util.ArrayList; +import java.util.Map; + + +public class AVD { + final static private int PHONE = 0; + final static private int WEAR = 1; + + final static public String DEFAULT_ABI = "x86"; + + public final static String DEFAULT_PHONE_PORT = "5566"; + public final static String DEFAULT_WEAR_PORT = "5576"; + + private static final String GETTING_START_TUT_URL = + "https://android.processing.org/tutorials/getting_started/index.html"; + + static final String DEVICE_DEFINITION = "pixel_6"; + static final String DEVICE_SKIN = "pixel_6"; + + static final String DEVICE_WEAR_DEFINITION = "wearos_square"; + static final String DEVICE_WEAR_SKIN = "wearos_square"; + + /** Name of this avd. */ + protected String name; + + protected String device; + protected String skin; + protected int type; + + static ArrayList avdList; + static ArrayList badList; + + /** "system-images;android-25;google_apis;x86" */ + static ArrayList wearImages; + static ArrayList phoneImages; + + private static Process process; + + /** Default virtual device used by Processing. */ + static public final AVD phoneAVD = + new AVD("processing-phone", + DEVICE_DEFINITION, DEVICE_SKIN, PHONE); + + /** Default virtual wear device used by Processing. */ + static public final AVD watchAVD = + new AVD("processing-watch", + DEVICE_WEAR_DEFINITION, DEVICE_WEAR_SKIN, WEAR); + + + public AVD(final String name, final String device, final String skin, int type) { + this.name = name; + this.device = device; + this.skin = skin; + this.type = type; + } + + + static public String getName(boolean wear) { + if (wear) { + return AVD.watchAVD.name; + } else { + return AVD.phoneAVD.name; + } + } + + static public String getTargetSDK(boolean wear, String abi) { + if (abi.equals("arm")) { + // The ARM images using Google APIs are too slow, so use the + // older Android (AOSP) images. + // TODO check if we can move to the regular ARM images... + return AndroidBuild.TARGET_WEAR_SDK_ARM; + } else if (abi.equals("arm64-v8a")) { + return wear ? AndroidBuild.TARGET_WEAR_SDK : AndroidBuild.TARGET_SDK; + } else { // x86 + return wear ? AndroidBuild.TARGET_WEAR_SDK : AndroidBuild.TARGET_SDK; + } + } + + static public String getPreferredPlatform(boolean wear, String abi) { + return "android-" + getTargetSDK(wear, abi); + } + + static public String getPreferredPort(boolean wear) { + String port = ""; + if (wear) { + port = Preferences.get("android.emulator.port.wear"); + if (port == null) { + port = DEFAULT_WEAR_PORT; + Preferences.set("android.emulator.port.wear", port); + } + } else { + port = Preferences.get("android.emulator.port.phone"); + if (port == null) { + port = DEFAULT_PHONE_PORT; + Preferences.set("android.emulator.port.phone", port); + } + } + return port; + } + + + static protected String getPreferredTag(boolean wear, String abi) { + if (wear) { + return "android-wear"; +// } else if (abi.contains("arm")) { +// // The ARM images are located in the default folder. No, apparently ARM images are in google_apis too +// return "default"; + } else { + return "google_apis"; + } + } + + + static protected String getPreferredABI() { + String abi = Preferences.get("android.emulator.image.abi"); + if (abi == null) { + abi = DEFAULT_ABI; + Preferences.set("android.emulator.image.abi", abi); + } + return abi; + } + + + static protected void list(final AndroidSDK sdk) throws IOException { + String prefABI = getPreferredABI(); + + try { + avdList = new ArrayList(); + badList = new ArrayList(); + File avdManager = sdk.getAVDManagerTool(); + ProcessBuilder pb = new ProcessBuilder(avdManager.getCanonicalPath(), "list", "avd"); + Map env = pb.environment(); + env.clear(); + env.put("JAVA_HOME", Platform.getJavaHome().getCanonicalPath()); + pb.redirectErrorStream(true); + + process = pb.start(); + + StringWriter outWriter = new StringWriter(); + new StreamPump(process.getInputStream(), "out: ").addTarget(outWriter).start(); + process.waitFor(); + + String[] lines = PApplet.split(outWriter.toString(), '\n'); + + if (process.exitValue() == 0) { + String name = ""; + String abi = ""; + boolean badness = false; + for (String line : lines) { + String[] m = PApplet.match(line, "\\s+Name\\:\\s+(\\S+)"); + String[] t = PApplet.match(line, "\\s+Tag/ABI\\:\\s+(\\S+)"); + + if (m != null) { + name = m[1]; + } + if (t != null) { + abi = t[1]; + if (-1 < abi.indexOf("/" + prefABI)) { + if (!badness) { +// System.out.println("good: " + m[1]); + avdList.add(name); + } else { +// System.out.println("bad: " + m[1]); + badList.add(name); + } +// } else { +// System.out.println("nope: " + line); + } + } + + + // "The following Android Virtual Devices could not be loaded:" + if (line.contains("could not be loaded:")) { +// System.out.println("starting the bad list"); +// System.err.println("Could not list AVDs:"); +// System.err.println(listResult); + badness = true; +// break; + } + } + } else { + System.err.println("Unhappy inside exists()"); + System.err.println(outWriter.toString()); + } + } catch (final InterruptedException ie) { } + finally { + process.destroy(); + } + } + + + protected boolean exists(final AndroidSDK sdk) throws IOException { + if (avdList == null) { + list(sdk); + } + for (String avd : avdList) { + if (Base.DEBUG) { + System.out.println("AVD.exists() checking for " + name + " against " + avd); + } + if (avd.equals(name)) { + return true; + } + } + return false; + } + + + /** + * Return true if a member of the renowned and prestigious + * "The following Android Virtual Devices could not be loaded:" club. + * (Prestigious may also not be the right word.) + */ + protected boolean badness() { + for (String avd : badList) { + if (avd.equals(name)) { + return true; + } + } + return false; + } + + + protected boolean hasImages(final AndroidSDK sdk) throws IOException { + String abi = getPreferredABI(); + if (type == PHONE) { + if (phoneImages == null) { + phoneImages = new ArrayList(); + getImages(phoneImages, sdk, abi); + } + return !phoneImages.isEmpty(); + } else { + if (wearImages == null) { + wearImages = new ArrayList(); + getImages(wearImages, sdk, abi); + } + return !wearImages.isEmpty(); + } + } + + + protected void refreshImages(final AndroidSDK sdk) throws IOException { + String abi = getPreferredABI(); + + if (type == PHONE) { + phoneImages = new ArrayList(); + getImages(phoneImages, sdk, abi); + } else { + wearImages = new ArrayList(); + getImages(wearImages, sdk, abi); + } + } + + + protected void getImages(final ArrayList images, final AndroidSDK sdk, + final String imageAbi) throws IOException { + final boolean wear = type == WEAR; + final String imagePlatform = getPreferredPlatform(wear, imageAbi); + final String imageTag = getPreferredTag(wear, imageAbi); + + final File avdManager = sdk.getAVDManagerTool(); + final String[] cmd = new String[] { + avdManager.getCanonicalPath(), + "create", "avd", + "-n", "dummy", + "-k", "dummy" + }; + + // Dummy avdmanager creation command to get the list of installed images, + // so far this is the only method available get that list. + ProcessBuilder pb = new ProcessBuilder(cmd); + + if (Base.DEBUG) { + System.out.println(processing.core.PApplet.join(cmd, " ")); + } + + Map env = pb.environment(); + env.clear(); + env.put("JAVA_HOME", Platform.getJavaHome().getCanonicalPath()); + pb.redirectErrorStream(true); + + try { + process = pb.start(); + StringWriter outWriter = new StringWriter(); + new StreamPump(process.getInputStream(), "out: ").addTarget(outWriter).start(); + process.waitFor(); + + String[] lines = PApplet.split(outWriter.toString(), '\n'); + for (String line : lines) { + if (images != null && + line.contains(";" + imagePlatform) && + line.contains(";" + imageTag) && + line.contains(";" + imageAbi)) { + images.add(line); + } + } + + } catch (final InterruptedException ie) { + ie.printStackTrace(); + } finally { + process.destroy(); + } + } + + + protected String getSdkId() throws IOException { + String abi = getPreferredABI(); + + if (type == PHONE) { + for (String image : phoneImages) { + if (image.contains(";" + abi)) return image; + } + } else { + for (String image : wearImages) { + if (image.contains(";" + abi)) return image; + } + } + + // Could not find any suitable package + return "null"; + } + + protected void copyDeviceSkins(final AndroidSDK sdk, final AndroidMode mode) { + File skinsFolder = new File(sdk.getFolder(), "skins"); + if (!skinsFolder.exists()) { + // The skins in this folder come from Android Studio, on Mac they are in the folder: + // /Applications/Android Studio.app/Contents/plugins/android/resources/device-art-resources + // Apparently the skins are not available as a SDK download. + File artFolder = new File(mode.getResourcesFolder(), "device-art-resources"); + AndroidUtil.copyDir(artFolder, skinsFolder); + } + } + + protected boolean create(final AndroidSDK sdk) throws IOException { + File sketchbookFolder = processing.app.Base.getSketchbookFolder(); + File androidFolder = new File(sketchbookFolder, "android"); + if (!androidFolder.exists()) androidFolder.mkdir(); + File avdPath = new File(androidFolder, "avd/" + name); + + File avdManager = sdk.getAVDManagerTool(); + final String[] cmd = new String[] { + avdManager.getCanonicalPath(), + "create", "avd", + "-n", name, + "-k", getSdkId(), + "-p", avdPath.getAbsolutePath(), + "-d", device, + "--skin", skin, + "--force" + }; + + ProcessBuilder pb = new ProcessBuilder(cmd); + + if (Base.DEBUG) { + System.out.println(processing.core.PApplet.join(cmd, " ")); + } + + // avdmanager create avd -n "Wear-Processing-0254" -k "system-images;android-25;google_apis;x86" -c 64M + // Set the list to null so that exists() will check again +// avdList = null; + + Map env = pb.environment(); + env.clear(); + env.put("JAVA_HOME", Platform.getJavaHome().getCanonicalPath()); + pb.redirectErrorStream(true); + + try { + process = pb.start(); + + // Passes 'no' to "Do you wish to create a custom hardware profile [no]" +// OutputStream os = process.getOutputStream(); +// PrintWriter pw = new PrintWriter(new OutputStreamWriter(os)); +// pw.println("no"); +// pw.flush(); +// pw.close(); +// os.flush(); +// os.close(); + + StringWriter outWriter = new StringWriter(); + new StreamPump(process.getInputStream(), "out: ").addTarget(outWriter).start(); + + process.waitFor(); + + if (process.exitValue() == 0) { + // Add skin to AVD's config file +// File configFile = new File(avdPath, "config.ini"); +// if (configFile.exists()) { +// try (PrintWriter output = new PrintWriter(new FileWriter(configFile.getAbsolutePath(), true))) { +// output.printf("%s\r\n", "skin.name=" + skin); +// } +// catch (Exception e) {} +// } + return true; + } + + if (outWriter.toString().contains("Package path is not valid")) { + // They didn't install the Google APIs + AndroidUtil.showMessage(AndroidMode.getTextString("android_avd.error.sdk_wrong_install_title"), + AndroidMode.getTextString("android_avd.error.sdk_wrong_install_body", GETTING_START_TUT_URL)); + } else { + // Just generally not working + AndroidUtil.showMessage(AndroidMode.getTextString("android_avd.error.cannot_create_avd_title"), + AndroidMode.getTextString("android_avd.error.cannot_create_avd_body", AndroidBuild.TARGET_SDK)); + } + System.err.println(outWriter.toString()); + } catch (final InterruptedException ie) { + ie.printStackTrace(); + } finally { + process.destroy(); + } + + return false; + } + + + static public boolean ensureProperAVD(final Frame window, final AndroidMode mode, + final AndroidSDK sdk, boolean wear) { + try { + AVD avd = wear ? watchAVD : phoneAVD; + if (avd.exists(sdk)) { + return true; + } + if (avd.badness()) { + AndroidUtil.showMessage(AndroidMode.getTextString("android_avd.error.cannot_load_avd_title"), + AndroidMode.getTextString("android_avd.error.cannot_load_avd_body")); + return false; + } + if (!avd.hasImages(sdk)) { + // Check that the AVD for the other kind of device has been already + // downloaded, and if so, the downloader should not ask for an + // ABI again. + AVD other = wear ? phoneAVD : watchAVD; + boolean ask = !other.hasImages(sdk); + boolean res = AndroidSDK.requestSysImage(window, mode, wear, ask); + if (!res) { + return false; + } else { + avd.refreshImages(sdk); + } + } + avd.copyDeviceSkins(sdk, mode); + if (avd.create(sdk)) { + return true; + } + } catch (final Exception e) { + e.printStackTrace(); + AndroidUtil.showMessage(AndroidMode.getTextString("android_avd.error.cannot_create_avd_title"), + AndroidMode.getTextString("android_avd.error.cannot_create_avd_body", AndroidBuild.TARGET_SDK)); + } + return false; + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidBuild.java b/processing/mode/src/processing/mode/android/AndroidBuild.java new file mode 100644 index 000000000..78a16f8a3 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidBuild.java @@ -0,0 +1,1137 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2009-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import org.gradle.tooling.*; + +import processing.app.Base; +import processing.app.Library; +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.Sketch; +import processing.app.SketchException; +import processing.app.Util; +import processing.core.PApplet; +import processing.mode.java.JavaBuild; +import processing.mode.java.preproc.PdePreprocessor; + +import java.io.*; +import java.util.HashMap; +import java.util.Properties; + +import java.util.*; +import processing.app.SketchCode; +import processing.mode.java.preproc.PreprocessorResult; + + +/** + * Class with all the infrastructure needed to build a sketch in the Android + * mode and run it either on the device or in the emulator, using Gradle as the + * build system. It also exports the sketch as a Gradle project file to build + * from the command line or import into Android Studio, and a signed and aligned + * package ready to upload to the Play Store. + */ +class AndroidBuild extends JavaBuild { + static public final int APP = 0; + static public final int WALLPAPER = 1; + static public final int WATCHFACE = 2; + static public final int VR = 3; + static public final int AR = 4; + + // Minimum SDK's API levels required for each component: + static public String MIN_SDK_APP; + static public String MIN_SDK_WALLPAPER; + static public String MIN_SDK_VR; + static public String MIN_SDK_AR; + static public String MIN_SDK_WATCHFACE; + + // Versions of all required dependencies + static public String TARGET_SDK; + static public String TARGET_WEAR_SDK_ARM; + static public String TARGET_WEAR_SDK; + static public String GRADLE_PLUGIN_VER; + static public String APPCOMPAT_VER; + static public String V4LEGACY_VER; + static public String PLAY_SERVICES_VER; + static public String WEAR_VER; + static public String GVR_VER; + static public String GAR_VER; + + // Main activity or service + static private final String APP_ACTIVITY_TEMPLATE = "AppActivity.java.tmpl"; + static private final String WALLPAPER_SERVICE_TEMPLATE = "WallpaperService.java.tmpl"; + static private final String WATCHFACE_SERVICE_TEMPLATE = "WatchFaceService.java.tmpl"; + static private final String VR_ACTIVITY_TEMPLATE = "VRActivity.java.tmpl"; + static private final String AR_ACTIVITY_TEMPLATE = "ARActivity.java.tmpl"; + + // Additional resources + static private final String LAYOUT_ACTIVITY_TEMPLATE = "LayoutActivity.xml.tmpl"; + static private final String STYLES_FRAGMENT_TEMPLATE = "StylesFragment.xml.tmpl"; + static private final String STYLES_VR_TEMPLATE = "StylesVR.xml.tmpl"; + static private final String STYLES_AR_TEMPLATE = "StylesAR.xml.tmpl"; + static private final String XML_WALLPAPER_TEMPLATE = "XMLWallpaper.xml.tmpl"; + static private final String STRINGS_WALLPAPER_TEMPLATE = "StringsWallpaper.xml.tmpl"; + static private final String XML_WATCHFACE_TEMPLATE = "XMLWatchFace.xml.tmpl"; + + // Gradle build files + static private final String GRADLE_SETTINGS_TEMPLATE = "Settings.gradle.tmpl"; + static private final String GRADLE_PROPERTIES_TEMPLATE = "Properties.gradle.tmpl"; + static private final String EXPORTED_GRADLE_PROPERTIES_TEMPLATE = "ExpProperties.gradle.tmpl"; + static private final String LOCAL_PROPERTIES_TEMPLATE = "Properties.local.tmpl"; + static private final String TOP_GRADLE_BUILD_TEMPLATE = "TopBuild.gradle.tmpl"; + static private final String APP_GRADLE_BUILD_ECJ_TEMPLATE = "AppBuildECJ.gradle.tmpl"; + static private final String APP_GRADLE_BUILD_TEMPLATE = "AppBuild.gradle.tmpl"; + static private final String VR_GRADLE_BUILD_ECJ_TEMPLATE = "VRBuildECJ.gradle.tmpl"; + static private final String VR_GRADLE_BUILD_TEMPLATE = "VRBuild.gradle.tmpl"; + static private final String AR_GRADLE_BUILD_ECJ_TEMPLATE = "ARBuildECJ.gradle.tmpl"; + static private final String AR_GRADLE_BUILD_TEMPLATE = "ARBuild.gradle.tmpl"; + static private final String WEAR_GRADLE_BUILD_ECJ_TEMPLATE = "WearBuildECJ.gradle.tmpl"; + static private final String WEAR_GRADLE_BUILD_TEMPLATE = "WearBuild.gradle.tmpl"; + + // Launcher and watch face icon files + static final String[] SKETCH_LAUNCHER_ICONS = {"launcher_36.png", "launcher_48.png", + "launcher_72.png", "launcher_96.png", + "launcher_144.png", "launcher_192.png"}; + static final String[] SKETCH_OLD_LAUNCHER_ICONS = {"icon-36.png", "icon-48.png", + "icon-72.png", "icon-96.png", + "icon-144.png", "icon-192.png"}; + static final String[] BUILD_LAUNCHER_ICONS = {"mipmap-ldpi/ic_launcher.png", "mipmap-mdpi/ic_launcher.png", + "mipmap-hdpi/ic_launcher.png", "mipmap-xhdpi/ic_launcher.png", + "mipmap-xxhdpi/ic_launcher.png", "mipmap-xxxhdpi/ic_launcher.png"}; + static final String[] SKETCH_WATCHFACE_ICONS = {"preview_circular.png", + "preview_rectangular.png"}; + static final String[] BUILD_WATCHFACE_ICONS = {"drawable-nodpi/preview_circular.png", + "drawable-nodpi/preview_rectangular.png"}; + + private int appComponent = APP; + + private final AndroidSDK sdk; + private final File coreZipFile; + + /** whether this is a "debug" or "release" build */ + private String target; + + /** The manifest for the sketch being built */ + private Manifest manifest; + + /** temporary folder safely inside a 8.3-friendly folder */ + private File tmpFolder; + + /** Determines which gradle build template will be used */ + private boolean exportProject = false; + + /** Renderer used by the sketch */ + private String renderer = ""; + + /** Name of the Gradle module in the project, either app or wear */ + private String module = ""; + + /** + * Constructor. + * @param sketch the sketch to be built + * @param mode reference to the mode + * @param appComp component (regular handheld app, wallpaper, watch face, VR, AR) + * @param emu build to run in emulator or on device if false + */ + public AndroidBuild(Sketch sketch, AndroidMode mode, int comp) { + super(sketch); + appComponent = comp; + sdk = mode.getSDK(); + coreZipFile = mode.getCoreZipLocation(); + module = appComponent == WATCHFACE ? "wear" : "app"; + } + + + public String getPackageName() { + return manifest.getPackageName(); + } + + + public int getAppComponent() { + return appComponent; + } + + + public boolean isWear() { + return appComponent == WATCHFACE; + } + + + public void cleanup() { + tmpFolder.deleteOnExit(); + } + + + public boolean usesOpenGL() { + return renderer != null && (renderer.equals("P2D") || renderer.equals("P3D")); + } + + + public String getPathForAPK() { + String suffix = target.equals("release") ? "release" : "debug"; + String apkName = getPathToAPK() + sketch.getName().toLowerCase() + "_" + suffix + ".apk"; + final File apkFile = new File(tmpFolder, apkName); + if (!apkFile.exists()) { + return null; + } + return apkFile.getAbsolutePath(); + } + + /** + * Build into temporary folders for building bundles (needed for the Windows 8.3 bugs in the Android SDK) + * @param target "debug" or "release" + * @throws SketchException + * @throws IOException + */ + public File buildBundle(String target, String password) throws IOException, SketchException { + this.target = target; + File folder = createProject(true, password); + if (folder == null) return null; + if (!gradleBuildBundle()) return null; + return folder; + } + + + /** + * Build into temporary folders (needed for the Windows 8.3 bugs in the Android SDK). + * @param target "debug" or "release" + * @throws SketchException + * @throws IOException + */ + public File build(String target, String password) throws IOException, SketchException { + this.target = target; + File folder = createProject(true, password); + if (folder == null) return null; + if (!gradleBuildPackage()) return null; + return folder; + } + + + /** + * Create an Gradle Android project folder, and run the preprocessor on the + * sketch. Creates the top and app modules in the case of regular, VR, AR and + * wallpapers, and top, mobile and wear modules in the case of watch faces. + */ + protected File createProject(boolean external, String password) + throws IOException, SketchException { + tmpFolder = createTempBuildFolder(sketch); + System.out.println(AndroidMode.getTextString("android_build.error.build_folder", tmpFolder.getAbsolutePath())); + + // Create the 'src' folder with the preprocessed code. + srcFolder = new File(tmpFolder, module + "/src/main/java"); + binFolder = srcFolder; // Needed in the the parent JavaBuild class + if (processing.app.Base.DEBUG) { + Platform.openFolder(tmpFolder); + } + + manifest = new Manifest(sketch, appComponent, mode.getFolder(), false); + + // build the preproc and get to work + String pckgName = getPackageName(); + PdePreprocessor preprocessor = PdePreprocessor.builderFor(sketch.getName()).setDestinationPackage(pckgName).build(); + PreprocessorResult result = preprocess(srcFolder, pckgName, preprocessor, false); + if (result != null) { + sketchClassName = result.getClassName(); + if (sketchClassName != null) { + renderer = result.getSketchRenderer(); + if (renderer == null) { + renderer = "JAVA2D"; + } + writeMainClass(srcFolder, external); + createTopModule("':" + module + "'", password); + createAppModule(module); + } + } + + return tmpFolder; + } + + + protected boolean gradleBuildBundle() throws SketchException { + ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(tmpFolder) + .connect(); + + boolean success = false; + BuildLauncher build = connection.newBuild(); + build.setStandardOutput(System.out); + build.setStandardError(System.err); + + try { + if (target.equals("debug")) build.forTasks("bundleDebug"); + else build.forTasks("bundleRelease"); + build.run(); + renameAAB(); + success = true; + } catch (org.gradle.tooling.UnsupportedVersionException e) { + e.printStackTrace(); + success = false; + } catch (org.gradle.tooling.BuildException e) { + e.printStackTrace(); + success = false; + } catch (org.gradle.tooling.BuildCancelledException e) { + e.printStackTrace(); + success = false; + } catch (org.gradle.tooling.GradleConnectionException e) { + e.printStackTrace(); + success = false; + } catch (Exception e) { + e.printStackTrace(); + success = false; + } finally { + connection.close(); + } + + try { + removeKeyPassword(); + } catch (IOException e) { + e.printStackTrace(); + } + + return success; + } + + + protected boolean gradleBuildPackage() throws SketchException { + ProjectConnection connection = GradleConnector.newConnector() + .forProjectDirectory(tmpFolder) + .connect(); + + boolean success = false; + BuildLauncher build = connection.newBuild(); + build.setStandardOutput(System.out); + build.setStandardError(System.err); + + try { + if (target.equals("debug")) build.forTasks("assembleDebug"); + else build.forTasks("assembleRelease"); + build.run(); + renameAPK(); + success = true; + } catch (org.gradle.tooling.UnsupportedVersionException e) { + e.printStackTrace(); + success = false; + } catch (org.gradle.tooling.BuildException e) { + e.printStackTrace(); + success = false; + } catch (org.gradle.tooling.BuildCancelledException e) { + e.printStackTrace(); + success = false; + } catch (org.gradle.tooling.GradleConnectionException e) { + e.printStackTrace(); + success = false; + } catch (Exception e) { + e.printStackTrace(); + success = false; + } finally { + connection.close(); + } + + try { + removeKeyPassword(); + } catch (IOException e) { + e.printStackTrace(); + } + + return success; + } + + + // --------------------------------------------------------------------------- + // Gradle modules + + + private void createTopModule(String projectModules, String keyPassword) + throws IOException { + HashMap replaceMap = new HashMap(); + + File buildTemplate = mode.getContentFile("templates/" + TOP_GRADLE_BUILD_TEMPLATE); + File buildlFile = new File(tmpFolder, "build.gradle"); + replaceMap.put("@@gradle_plugin_version@@", GRADLE_PLUGIN_VER); + AndroidUtil.createFileFromTemplate(buildTemplate, buildlFile, replaceMap); + +// File gradlePropsTemplate = mode.getContentFile("templates/" + GRADLE_PROPERTIES_TEMPLATE); +// File gradlePropsFile = new File(tmpFolder, "gradle.properties"); +// Util.copyFile(gradlePropsTemplate, gradlePropsFile); + File gradlePropsTemplate; + if (exportProject) { + gradlePropsTemplate = mode.getContentFile("templates/" + EXPORTED_GRADLE_PROPERTIES_TEMPLATE); + } else { + gradlePropsTemplate = mode.getContentFile("templates/" + GRADLE_PROPERTIES_TEMPLATE); + } + File gradlePropsFile = new File(tmpFolder, "gradle.properties"); + String javaHome = Platform.getJavaHome().getAbsolutePath(); + replaceMap.clear(); + replaceMap.put("@@java_home@@", javaHome); + if (!keyPassword.equals("") && AndroidKeyStore.getKeyStore() != null) { + replaceMap.put("@@keystore_file@@", AndroidKeyStore.getKeyStore().getAbsolutePath().replace('\\', '/')); + replaceMap.put("@@key_alias@@", AndroidKeyStore.ALIAS_STRING); + replaceMap.put("@@key_password@@", keyPassword); + } + AndroidUtil.createFileFromTemplate(gradlePropsTemplate, gradlePropsFile, replaceMap); + + File settingsTemplate = mode.getContentFile("templates/" + GRADLE_SETTINGS_TEMPLATE); + File settingsFile = new File(tmpFolder, "settings.gradle"); + replaceMap.clear(); + if (getAppComponent() == VR) { + // The local google-vr has to be added to the settings to fix Issue #718 + replaceMap.put("@@project_modules@@", projectModules + ", ':app:libs:google-vr'"); + } else { + replaceMap.put("@@project_modules@@", projectModules); + } + + AndroidUtil.createFileFromTemplate(settingsTemplate, settingsFile, replaceMap); + + File localPropsTemplate = mode.getContentFile("templates/" + LOCAL_PROPERTIES_TEMPLATE); + File localPropsFile = new File(tmpFolder, "local.properties"); + replaceMap.clear(); + final String sdkPath = sdk.getFolder().getAbsolutePath(); + if (Platform.isWindows()) { + // Windows needs backslashes escaped, or it will also accept forward + // slashes in the build file. We're using the forward slashes since this + // path gets concatenated with a lot of others that use forwards anyway. + replaceMap.put("@@sdk_path@@", sdkPath.replace('\\', '/')); + } else { + replaceMap.put("@@sdk_path@@", sdkPath); + } + AndroidUtil.createFileFromTemplate(localPropsTemplate, localPropsFile, replaceMap); + } + + + private void createAppModule(String moduleName) + throws SketchException, IOException { + File moduleFolder = AndroidUtil.createPath(tmpFolder, moduleName); + + String minSdk; + String tmplFile; + if (appComponent == AR) { + minSdk = MIN_SDK_AR; + tmplFile = exportProject ? AR_GRADLE_BUILD_TEMPLATE : AR_GRADLE_BUILD_ECJ_TEMPLATE; + } else if (appComponent == VR) { + minSdk = MIN_SDK_VR; + tmplFile = exportProject ? VR_GRADLE_BUILD_TEMPLATE : VR_GRADLE_BUILD_ECJ_TEMPLATE; + } else if (appComponent == WATCHFACE) { + minSdk = MIN_SDK_WATCHFACE; + tmplFile = exportProject ? WEAR_GRADLE_BUILD_TEMPLATE : WEAR_GRADLE_BUILD_ECJ_TEMPLATE; + } else { + minSdk = MIN_SDK_APP; + tmplFile = exportProject ? APP_GRADLE_BUILD_TEMPLATE : APP_GRADLE_BUILD_ECJ_TEMPLATE; + } + + String modePath = new File(mode.getFolder(), "mode").getPath().replace('\\', '/'); + String toolPath = Base.getToolsFolder().getPath().replace('\\', '/'); + String platformPath = sdk.getTargetPlatform(TARGET_SDK).getPath().replace('\\', '/'); + + File appBuildTemplate = mode.getContentFile("templates/" + tmplFile); + File appBuildFile = new File(moduleFolder, "build.gradle"); + HashMap replaceMap = new HashMap(); + replaceMap.put("@@mode_folder@@", modePath); + replaceMap.put("@@tools_folder@@", toolPath); + replaceMap.put("@@target_platform@@", platformPath); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@min_sdk@@", minSdk); + replaceMap.put("@@target_sdk@@", TARGET_SDK); + replaceMap.put("@@appcompat_version@@", APPCOMPAT_VER); + replaceMap.put("@@v4legacy_version@@", V4LEGACY_VER); + replaceMap.put("@@play_services_version@@", PLAY_SERVICES_VER); + replaceMap.put("@@wear_version@@", WEAR_VER); + replaceMap.put("@@gvr_version@@", GVR_VER); + replaceMap.put("@@gar_version@@", GAR_VER); + replaceMap.put("@@version_code@@", manifest.getVersionCode()); + replaceMap.put("@@version_name@@", manifest.getVersionName()); + AndroidUtil.createFileFromTemplate(appBuildTemplate, appBuildFile, replaceMap); + + AndroidUtil.writeFile(new File(moduleFolder, "proguard-rules.pro"), + new String[]{"# Add project specific ProGuard rules here."}); + + File libsFolder = AndroidUtil.createPath(moduleFolder, "libs"); + File mainFolder = new File(moduleFolder, "src/main"); + File resFolder = AndroidUtil.createPath(mainFolder, "res"); + File assetsFolder = AndroidUtil.createPath(mainFolder, "assets"); + + writeRes(resFolder); + + File tempManifest = new File(mainFolder, "AndroidManifest.xml"); + manifest.writeCopy(tempManifest, sketchClassName); + + Util.copyFile(coreZipFile, new File(libsFolder, "processing-core.jar")); + + // Copy any imported libraries (their libs and assets), + // and anything in the code folder contents to the project. + copyImportedLibs(libsFolder, mainFolder, assetsFolder); + copyCodeFolder(libsFolder); + + if (getAppComponent() == VR) { + // Need to call this to fix Issue #718 + copyGVRLibs(libsFolder); + } + + // Copy the data folder (if one exists) to the project's 'assets' folder + final File sketchDataFolder = sketch.getDataFolder(); + if (sketchDataFolder.exists()) { + Util.copyDir(sketchDataFolder, assetsFolder); + } + + // Do the same for the 'res' folder. The user can copy an entire res folder + // into the sketch's folder, and it will be used in the project! + final File sketchResFolder = new File(sketch.getFolder(), "res"); + if (sketchResFolder.exists()) { + Util.copyDir(sketchResFolder, resFolder); + } + } + + + // --------------------------------------------------------------------------- + // Templates + + + private void writeMainClass(final File srcDirectory, final boolean external) { + int comp = getAppComponent(); + String[] permissions = manifest.getPermissions(); + if (comp == APP) { + writeFragmentActivity(srcDirectory, permissions, external); + } else if (comp == WALLPAPER) { + writeWallpaperService(srcDirectory, permissions, external); + } else if (comp == WATCHFACE) { + if (usesOpenGL()) { + writeWatchFaceGLESService(srcDirectory, permissions, external); + } else { + writeWatchFaceCanvasService(srcDirectory, permissions, external); + } + } else if (comp == VR) { + writeVRActivity(srcDirectory, permissions, external); + } else if (comp == AR) { + writeARActivity(srcDirectory, permissions, external); + } + } + + + private void writeFragmentActivity(final File srcDirectory, + final String[] permissions, final boolean external) { + File javaTemplate = mode.getContentFile("templates/" + APP_ACTIVITY_TEMPLATE); + File javaFile = new File(new File(srcDirectory, getPackageName().replace(".", "/")), "MainActivity.java"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@sketch_class_name@@", sketchClassName); + replaceMap.put("@@external@@", external ? "sketch.setExternal(true);" : ""); + + AndroidUtil.createFileFromTemplate(javaTemplate, javaFile, replaceMap); + } + + + private void writeWallpaperService(final File srcDirectory, + String[] permissions, final boolean external) { + File javaTemplate = mode.getContentFile("templates/" + WALLPAPER_SERVICE_TEMPLATE); + File javaFile = new File(new File(srcDirectory, getPackageName().replace(".", "/")), "MainService.java"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@sketch_class_name@@", sketchClassName); + replaceMap.put("@@external@@", external ? "sketch.setExternal(true);" : ""); + + AndroidUtil.createFileFromTemplate(javaTemplate, javaFile, replaceMap); + } + + + private void writeWatchFaceGLESService(final File srcDirectory, + String[] permissions, final boolean external) { + File javaTemplate = mode.getContentFile("templates/" + WATCHFACE_SERVICE_TEMPLATE); + File javaFile = new File(new File(srcDirectory, getPackageName().replace(".", "/")), "MainService.java"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@watchface_classs@@", "PWatchFaceGLES"); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@sketch_class_name@@", sketchClassName); + replaceMap.put("@@external@@", external ? "sketch.setExternal(true);" : ""); + + AndroidUtil.createFileFromTemplate(javaTemplate, javaFile, replaceMap); + } + + + private void writeWatchFaceCanvasService(final File srcDirectory, + String[] permissions, final boolean external) { + File javaTemplate = mode.getContentFile("templates/" + WATCHFACE_SERVICE_TEMPLATE); + File javaFile = new File(new File(srcDirectory, getPackageName().replace(".", "/")), "MainService.java"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@watchface_classs@@", "PWatchFaceCanvas"); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@sketch_class_name@@", sketchClassName); + replaceMap.put("@@external@@", external ? "sketch.setExternal(true);" : ""); + + AndroidUtil.createFileFromTemplate(javaTemplate, javaFile, replaceMap); + } + + + private void writeVRActivity(final File srcDirectory, String[] permissions, + final boolean external) { + File javaTemplate = mode.getContentFile("templates/" + VR_ACTIVITY_TEMPLATE); + File javaFile = new File(new File(srcDirectory, getPackageName().replace(".", "/")), "MainActivity.java"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@sketch_class_name@@", sketchClassName); + replaceMap.put("@@external@@", external ? "sketch.setExternal(true);" : ""); + + AndroidUtil.createFileFromTemplate(javaTemplate, javaFile, replaceMap); + } + + private void writeARActivity(final File srcDirectory, String[] permissions, + final boolean external) { + File javaTemplate = mode.getContentFile("templates/" + AR_ACTIVITY_TEMPLATE); + File javaFile = new File(new File(srcDirectory, getPackageName().replace(".", "/")), "MainActivity.java"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@package_name@@", getPackageName()); + replaceMap.put("@@sketch_class_name@@", sketchClassName); + replaceMap.put("@@external@@", external ? "sketch.setExternal(true);" : ""); + + AndroidUtil.createFileFromTemplate(javaTemplate, javaFile, replaceMap); + } + + + private void writeResLayoutMainActivity(final File layoutFolder) { + File xmlTemplate = mode.getContentFile("templates/" + LAYOUT_ACTIVITY_TEMPLATE); + File xmlFile = new File(layoutFolder, "main.xml"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@sketch_class_name@@",sketchClassName); + + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile, replaceMap); + } + + + private void writeResStylesFragment(final File valuesFolder) { + File xmlTemplate = mode.getContentFile("templates/" + STYLES_FRAGMENT_TEMPLATE); + File xmlFile = new File(valuesFolder, "styles.xml"); + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile); + } + + + private void writeResStylesVR(final File valuesFolder) { + File xmlTemplate = mode.getContentFile("templates/" + STYLES_VR_TEMPLATE); + File xmlFile = new File(valuesFolder, "styles.xml"); + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile); + } + + + private void writeResStylesAR(final File valuesFolder) { + File xmlTemplate = mode.getContentFile("templates/" + STYLES_AR_TEMPLATE); + File xmlFile = new File(valuesFolder, "styles.xml"); + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile); + } + + + private void writeResXMLWallpaper(final File xmlFolder) { + File xmlTemplate = mode.getContentFile("templates/" + XML_WALLPAPER_TEMPLATE); + File xmlFile = new File(xmlFolder, "wallpaper.xml"); + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile); + } + + + private void writeResStringsWallpaper(final File valuesFolder) { + File xmlTemplate = mode.getContentFile("templates/" + STRINGS_WALLPAPER_TEMPLATE); + File xmlFile = new File(valuesFolder, "strings.xml"); + + HashMap replaceMap = new HashMap(); + replaceMap.put("@@sketch_class_name@@",sketchClassName); + + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile, replaceMap); + } + + + private void writeResXMLWatchFace(final File xmlFolder) { + File xmlTemplate = mode.getContentFile("templates/" + XML_WATCHFACE_TEMPLATE); + File xmlFile = new File(xmlFolder, "watch_face.xml"); + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile); + } + + + private void writeRes(File resFolder) throws SketchException { + File layoutFolder = AndroidUtil.createPath(resFolder, "layout"); + writeResLayoutMainActivity(layoutFolder); + + int comp = getAppComponent(); + if (comp == APP) { + File valuesFolder = AndroidUtil.createPath(resFolder, "values"); + writeResStylesFragment(valuesFolder); + } else if (comp == WALLPAPER) { + File xmlFolder = AndroidUtil.createPath(resFolder, "xml"); + writeResXMLWallpaper(xmlFolder); + File valuesFolder = AndroidUtil.createPath(resFolder, "values"); + writeResStringsWallpaper(valuesFolder); + } else if (comp == WATCHFACE) { + File xmlFolder = AndroidUtil.createPath(resFolder, "xml"); + writeResXMLWatchFace(xmlFolder); + } else if (comp == VR) { + File valuesFolder = AndroidUtil.createPath(resFolder, "values"); + writeResStylesVR(valuesFolder); + } else if (comp == AR) { + File valuesFolder = AndroidUtil.createPath(resFolder, "values"); + writeResStylesAR(valuesFolder); + } + + File sketchFolder = sketch.getFolder(); + writeLauncherIconFiles(sketchFolder, resFolder); + if (comp == WATCHFACE) { + // Need the preview icons for watch faces. + writeWatchFaceIconFiles(sketchFolder, resFolder); + } + } + + + // --------------------------------------------------------------------------- + // Icons + + + private void writeLauncherIconFiles(File sketchFolder, File resFolder) { + writeIconFiles(sketchFolder, resFolder, SKETCH_LAUNCHER_ICONS, SKETCH_OLD_LAUNCHER_ICONS, BUILD_LAUNCHER_ICONS); + } + + + private void writeWatchFaceIconFiles(File sketchFolder, File resFolder) { + writeIconFiles(sketchFolder, resFolder, SKETCH_WATCHFACE_ICONS, null, BUILD_WATCHFACE_ICONS); + } + + + private void writeIconFiles(File sketchFolder, File resFolder, + String[] sketchIconNames, String[] oldIconNames, String[] buildIconNames) { + File[] localIcons = AndroidUtil.getFileList(sketchFolder, sketchIconNames, oldIconNames); + File[] buildIcons = AndroidUtil.getFileList(resFolder, buildIconNames); + if (AndroidUtil.noFileExists(localIcons)) { + // If no icons are in the sketch folder, then copy all the defaults + File[] defaultIcons = AndroidUtil.getFileList(mode, "icons/", sketchIconNames); + try { + for (int i = 0; i < localIcons.length; i++) { + copyIcon(defaultIcons[i], buildIcons[i]); + } + } catch (IOException e) { + e.printStackTrace(); + } + } else { + // If at least one of the icons already exists, then use that across the board + try { + for (int i = 0; i < localIcons.length; i++) { + if (localIcons[i].exists()) copyIcon(localIcons[i], buildIcons[i]); + } + } catch (IOException e) { + System.err.println(AndroidMode.getTextString("android_build.error.cannot_copy_icons")); + e.printStackTrace(); + } + } + } + + + private void copyIcon(File srcFile, File destFile) throws IOException { + File parent = destFile.getParentFile(); + if (parent.exists() || parent.mkdirs()) { + Util.copyFile(srcFile, destFile); + } else { + System.err.println(AndroidMode.getTextString("android_build.error.cannot_create_icon_folder", destFile.getParentFile())); + } + } + + + // --------------------------------------------------------------------------- + // Export project + + + public File exportProject() throws IOException, SketchException { + target = "debug"; + + exportProject = true; + File projectFolder = createProject(false, ""); + exportProject = false; + + File exportFolder = createExportFolder("android"); + Util.copyDir(projectFolder, exportFolder); + installGradlew(exportFolder); + return exportFolder; + } + + + // --------------------------------------------------------------------------- + // Export bundle + + + public File exportBundle(String keyStorePassword) throws Exception { + File projectFolder = buildBundle("release", keyStorePassword); + if (projectFolder == null) return null; + + // Final export folder + File exportFolder = createExportFolder("buildBundle"); + Util.copyDir(new File(projectFolder, getPathToAAB()), exportFolder); + return exportFolder; + } + + + // --------------------------------------------------------------------------- + // Export package + + + public File exportPackage(String keyStorePassword) throws Exception { + File projectFolder = build("release", keyStorePassword); + if (projectFolder == null) return null; + + // Final export folder + File exportFolder = createExportFolder("buildPackage"); + Util.copyDir(new File(projectFolder, getPathToAPK()), exportFolder); + return exportFolder; + } + + + //--------------------------------------------------------------------------- + // Build utils + + + /** + * Tell the PDE to not complain about android.* packages and others that are + * part of the OS library set as if they're missing. + */ + protected boolean ignorableImport(String pkg) { + if (pkg.startsWith("android.")) return true; + if (pkg.startsWith("java.")) return true; + if (pkg.startsWith("javax.")) return true; + if (pkg.startsWith("org.apache.http.")) return true; + if (pkg.startsWith("org.json.")) return true; + if (pkg.startsWith("org.w3c.dom.")) return true; + if (pkg.startsWith("org.xml.sax.")) return true; + + if (pkg.startsWith("processing.core.")) return true; + if (pkg.startsWith("processing.data.")) return true; + if (pkg.startsWith("processing.event.")) return true; + if (pkg.startsWith("processing.opengl.")) return true; + + return false; + } + + + /** + * For each library, copy .jar and .zip files to the 'libs' folder, + * and copy anything else to the 'assets' folder. + */ + private void copyImportedLibs(final File libsFolder, + final File mainFolder, + final File assetsFolder) throws IOException { + for (Library library : getImportedLibraries()) { + // Add each item from the library folder / export list to the output + for (File exportFile : library.getApplicationExports("armeabi")) { + copyImportedLib(libsFolder, mainFolder, assetsFolder, exportFile); + } + for (File exportFile : library.getApplicationExports("armeabi-v7a")) { + copyImportedLib(libsFolder, mainFolder, assetsFolder, exportFile); + } + for (File exportFile : library.getApplicationExports("x86")) { + copyImportedLib(libsFolder, mainFolder, assetsFolder, exportFile); + } + for (File exportFile : library.getApplicationExports("arm64-v8a")) { + copyImportedLib(libsFolder, mainFolder, assetsFolder, exportFile); + } + for (File exportFile : library.getApplicationExports("x86_64")) { + copyImportedLib(libsFolder, mainFolder, assetsFolder, exportFile); + } + } + } + + private void copyImportedLib(final File libsFolder, + final File mainFolder, + final File assetsFolder, + final File exportFile) throws IOException { + String exportName = exportFile.getName(); + + // Skip the GVR and ARCore jars, because gradle will resolve the dependencies + if (appComponent == VR && exportName.toLowerCase().startsWith("sdk")) return; + if (appComponent == AR && exportName.toLowerCase().startsWith("core")) return; + + if (!exportFile.exists()) { + System.err.println(AndroidMode.getTextString("android_build.error.export_file_does_not_exist", exportFile.getName())); + } else if (exportFile.isDirectory()) { + // Copy native library folders to the correct location + if (exportName.equals("armeabi") || + exportName.equals("armeabi-v7a") || + exportName.equals("x86") || + exportName.equals("arm64-v8a") || + exportName.equals("x86_64")) + { + Util.copyDir(exportFile, new File(libsFolder, exportName)); + } + // Copy jni libraries (.so files) to the correct location + else if (exportName.equals("jniLibs")) { + Util.copyDir(exportFile, new File(mainFolder, exportName)); + } + else { + // Copy any other directory to the assets folder + Util.copyDir(exportFile, new File(assetsFolder, exportName)); + } + } else if (exportName.toLowerCase().endsWith(".zip")) { + // As of r4 of the Android SDK, it looks like .zip files + // are ignored in the libs folder, so rename to .jar + System.err.println(AndroidMode.getTextString("android_build.error.zip_files_not_allowed", exportFile.getName())); + String jarName = exportName.substring(0, exportName.length() - 4) + ".jar"; + Util.copyFile(exportFile, new File(libsFolder, jarName)); + + } else if (exportName.toLowerCase().endsWith(".jar")) { + Util.copyFile(exportFile, new File(libsFolder, exportName)); + + } else { + Util.copyFile(exportFile, new File(assetsFolder, exportName)); + } + } + + /** + * Copy the dummy Gradle project containing aar files from Google VR, + * so they can be imported locally from the project + */ + private void copyGVRLibs(final File libsFolder) throws IOException { + File srcFolder = new File(mode.getFolder(), "libraries/vr/libs/google-vr"); + File dstFolder = new File(libsFolder, "google-vr"); + Util.copyDir(srcFolder, dstFolder); + } + + private void copyCodeFolder(final File libsFolder) throws IOException { + // Copy files from the 'code' directory into the 'libs' folder + final File codeFolder = sketch.getCodeFolder(); + if (codeFolder != null && codeFolder.exists()) { + for (final File item : codeFolder.listFiles()) { + if (!item.isDirectory()) { + final String name = item.getName(); + final String lcname = name.toLowerCase(); + if (lcname.endsWith(".jar") || lcname.endsWith(".zip")) { + String jarName = name.substring(0, name.length() - 4) + ".jar"; + Util.copyFile(item, new File(libsFolder, jarName)); + } + } + } + } + } + + private void renameAAB() { + String suffix = target.equals("release") ? "release" : "debug"; + String aabName = getPathToAAB() + module + "-" + suffix + ".aab"; + final File aabFile = new File(tmpFolder, aabName); + if (aabFile.exists()) { + String suffixNew = target.equals("release") ? "release" : "debug"; + String aabNameNew = getPathToAAB() + + sketch.getName().toLowerCase() + "_" + suffixNew + ".aab"; + final File aabFileNew = new File(tmpFolder, aabNameNew); + aabFile.renameTo(aabFileNew); + } + } + + private String getPathToAAB() { + return module + "/build/outputs/bundle/" + target + "/"; + } + + + private void renameAPK() { + String suffix = target.equals("release") ? "release" : "debug"; + String apkName = getPathToAPK() + module + "-" + suffix + ".apk"; + final File apkFile = new File(tmpFolder, apkName); + if (apkFile.exists()) { + String suffixNew = target.equals("release") ? "release" : "debug"; + String apkNameNew = getPathToAPK() + + sketch.getName().toLowerCase() + "_" + suffixNew + ".apk"; + final File apkFileNew = new File(tmpFolder, apkNameNew); + apkFile.renameTo(apkFileNew); + } + } + + + private void removeKeyPassword() throws IOException { + File gradlePropsTemplate = mode.getContentFile("templates/" + GRADLE_PROPERTIES_TEMPLATE); + File gradlePropsFile = new File(tmpFolder, "gradle.properties"); + Util.copyFile(gradlePropsTemplate, gradlePropsFile); + } + + + private String getPathToAPK() { + return module + "/build/outputs/apk/" + target + "/"; + } + + + /** + * The Android dex util pukes on paths containing spaces, which will happen + * most of the time on Windows, since Processing sketches wind up in + * "My Documents". Therefore, build android in a temp file. + * http://code.google.com/p/android/issues/detail?id=4567 + * + * @param sketch + * @return A folder in which to build the android sketch + * @throws IOException + */ + private File createTempBuildFolder(final Sketch sketch) throws IOException { + final File tmp = File.createTempFile("android", "sketch"); + if (!(tmp.delete() && tmp.mkdir())) { + throw new IOException(AndroidMode.getTextString("android_build.error.cannot_create_build_folder", tmp)); + } + return tmp; + } + + + private void installGradlew(File exportFolder) throws IOException { + File gradlewFile = mode.getContentFile("mode/gradlew.zip"); + AndroidUtil.extractFolder(gradlewFile, exportFolder); + if (Platform.isMacOS() || Platform.isLinux()) { + File execFile = new File(exportFolder, "gradlew"); + execFile.setExecutable(true); + } + } + + + private File createExportFolder(String name) throws IOException { + return AndroidUtil.createSubFolder(sketch.getFolder(), name); + } + + + static public void initVersions(File file) { + InputStream input; + try { + input = new FileInputStream(file); + Properties props = new Properties(); + props.load(input); + + MIN_SDK_APP = props.getProperty("android-min-app"); + MIN_SDK_WALLPAPER = props.getProperty("android-min-wallpaper"); + MIN_SDK_VR = props.getProperty("android-min-vr"); + MIN_SDK_AR = props.getProperty("android-min-ar"); + MIN_SDK_WATCHFACE = props.getProperty("android-min-wear"); + + // Versions strings of all dependencies are stored in a preferences file so they can be changed by the + // user without having to rebuild/reinstall the mode. + + GRADLE_PLUGIN_VER = Preferences.get("android.gradle_plugin"); + String defGradlePluginVersion = props.getProperty("android-gradle-plugin"); + if (GRADLE_PLUGIN_VER == null || PApplet.parseInt(GRADLE_PLUGIN_VER) != PApplet.parseInt(defGradlePluginVersion)) { + GRADLE_PLUGIN_VER = defGradlePluginVersion; + Preferences.set("android.gradle_plugin", GRADLE_PLUGIN_VER); + } + + TARGET_SDK = Preferences.get("android.sdk.target"); + String defTargetSDK = props.getProperty("android-platform"); + if (TARGET_SDK == null || PApplet.parseInt(TARGET_SDK) != PApplet.parseInt(defTargetSDK)) { + TARGET_SDK = defTargetSDK; + Preferences.set("android.sdk.target", TARGET_SDK); + } + + TARGET_WEAR_SDK_ARM = Preferences.get("android.sdk.target.wear_arm"); + String defTargetWearSDKArm = props.getProperty("android-platform-wear-arm"); + if (TARGET_WEAR_SDK_ARM == null || PApplet.parseInt(TARGET_WEAR_SDK_ARM) != PApplet.parseInt(defTargetWearSDKArm)) { + TARGET_WEAR_SDK_ARM = defTargetWearSDKArm; + Preferences.set("android.sdk.target.wear_arm", TARGET_WEAR_SDK_ARM); + } + + TARGET_WEAR_SDK = Preferences.get("android.sdk.target.wear"); + String defTargetWearSDK = props.getProperty("android-platform-wear"); + if (TARGET_WEAR_SDK == null || PApplet.parseInt(TARGET_WEAR_SDK) != PApplet.parseInt(defTargetWearSDK)) { + TARGET_WEAR_SDK = defTargetWearSDK; + Preferences.set("android.sdk.target.wear", TARGET_WEAR_SDK); + } + + APPCOMPAT_VER = Preferences.get("android.sdk.appcompat"); + String defAppCompatVer = props.getProperty("androidx.appcompat%appcompat"); + if (APPCOMPAT_VER == null || !versionCheck(APPCOMPAT_VER, defAppCompatVer)) { + APPCOMPAT_VER = defAppCompatVer; + Preferences.set("android.sdk.appcompat", APPCOMPAT_VER); + } + + V4LEGACY_VER = Preferences.get("android.sdk.v4legacy"); + String defV4LegacyVer = props.getProperty("androidx.legacy%legacy-support-v4"); + if (V4LEGACY_VER == null || !versionCheck(V4LEGACY_VER, defV4LegacyVer)) { + V4LEGACY_VER = defV4LegacyVer; + Preferences.set("android.sdk.v4legacy", V4LEGACY_VER); + } + + PLAY_SERVICES_VER = Preferences.get("android.sdk.play_services"); + String defPlayServicesVer = props.getProperty("com.google.android.gms%play-services-wearable"); + if (PLAY_SERVICES_VER == null || !versionCheck(PLAY_SERVICES_VER, defPlayServicesVer)) { + PLAY_SERVICES_VER = defPlayServicesVer; + Preferences.set("android.sdk.play_services", PLAY_SERVICES_VER); + } + + WEAR_VER = Preferences.get("android.sdk.wear"); + String defWearVer = props.getProperty("com.google.android.support%wearable"); + if (WEAR_VER == null || !versionCheck(WEAR_VER, defWearVer)) { + WEAR_VER = defWearVer; + Preferences.set("android.sdk.wear", WEAR_VER); + } + + GVR_VER = Preferences.get("android.sdk.gvr"); + String defVRVer = props.getProperty("com.google.vr"); + if (GVR_VER == null || !versionCheck(GVR_VER, defVRVer)) { + GVR_VER = defVRVer; + Preferences.set("android.sdk.gvr", GVR_VER); + } + + GAR_VER = Preferences.get("android.sdk.ar"); + String defARVer = props.getProperty("com.google.ar"); + if (GAR_VER == null || !versionCheck(GAR_VER, defARVer)) { + GAR_VER = defARVer; + Preferences.set("android.sdk.ar", GAR_VER); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + static private boolean versionCheck(String currentVersion, String minVersion) { + String[] currentPieces = currentVersion.split("\\."); + String[] minPieces = minVersion.split("\\."); + + if (currentPieces.length == 3 && minPieces.length == 3) { + int currentMajor = PApplet.parseInt(currentPieces[0], -1); + int currentMinor = PApplet.parseInt(currentPieces[1], -1); + int currentMicro = PApplet.parseInt(currentPieces[2], -1); + + int minMajor = PApplet.parseInt(minPieces[0], -1); + int minMinor = PApplet.parseInt(minPieces[1], -1); + int minMicro = PApplet.parseInt(minPieces[2], -1); + + if (-1 < currentMajor && -1 < currentMinor && -1 < currentMicro && + -1 < minMajor && -1 < minMinor && -1 < minMicro) { + if (currentMajor < minMajor) { + return false; + } else if (currentMajor == minMajor) { + if (currentMinor < minMinor) { + return false; + } if (currentMinor == minMinor) { + if (currentMicro < minMicro) { + return false; + } else { + return true; + } + } else { + return true; + } + } else { + return true; + } + } + } + + return false; + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidDebugger.java b/processing/mode/src/processing/mode/android/AndroidDebugger.java new file mode 100644 index 000000000..f68766cca --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidDebugger.java @@ -0,0 +1,411 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2018-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import com.sun.jdi.*; +import com.sun.jdi.event.*; +import com.sun.jdi.event.Event; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequestManager; +import com.sun.jdi.request.StepRequest; +import processing.app.Language; +import processing.app.Messages; +import processing.mode.java.debug.*; + +import javax.swing.*; +import java.awt.*; +import java.io.IOException; + +// Developed by Manav Jain as part of GSoC 2018 +public class AndroidDebugger extends Debugger { + /// editor window, acting as main view + protected AndroidEditor editor; + protected AndroidRunner runtime; + protected AndroidMode androidMode; + + protected boolean isEnabled; + + + private String pkgName = ""; + private String sketchClassName = ""; + + public AndroidDebugger(AndroidEditor editor, AndroidMode androidMode) { + super(editor); + this.editor = editor; + this.androidMode = androidMode; + } + + public boolean isEnabled() { + return isEnabled; + } + + public void toggleDebug() { + isEnabled = !isEnabled; + inspector.setVisible(enabled); + if (isEnabled) { + debugItem.setText(Language.text("menu.debug.disable")); + } else { + debugItem.setText(Language.text("menu.debug.enable")); + } + + for (Component item : debugMenu.getMenuComponents()) { + if (item instanceof JMenuItem && item != debugItem) { + item.setEnabled(isEnabled); + } + } + + } + + @Override + public AndroidEditor getEditor() { + return editor; + } + + public synchronized void startDebug(AndroidRunner runner, Device device) { + //stopDebug(); // stop any running sessions + if (isStarted()) { + return; // do nothing + } + + inspector.reset(); + + // make the inspector instance visible on which tree nodes would be reflected + inspector.setVisible(true); + + runtime = runner; + pkgName = runner.build.getPackageName(); + sketchClassName = runner.build.getSketchClassName(); + + mainClassName = pkgName + "." + sketchClassName; + + try { + int port = 8000 + (int) (Math.random() * 1000); + device.forwardPort(port); + + // connect + System.out.println(AndroidMode.getTextString("android_debugger.info.attaching_debugger")); + VirtualMachine vm = runner.connectVirtualMachine(port); + System.out.println(AndroidMode.getTextString("android_debugger.info.debugger_attached")); + + // start receiving vm events + VMEventReader eventThread = new VMEventReader(vm.eventQueue(), vmEventListener); + eventThread.start(); + + // watch for loaded classes + addClassWatch(vm); + + // resume the vm + vm.resume(); + + } catch (IOException e) { + Messages.log(AndroidMode.getTextString("android_debugger.error.debugger_exception", e.getMessage())); + // Retry + startDebug(runner, device); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + @Override public synchronized void vmEvent(EventSet es) { + VirtualMachine vm = vm(); + if (vm != null && vm != es.virtualMachine()) { + // This is no longer VM we are interested in, + // we already cleaned up and run different VM now. + return; + } + for (Event e : es) { + // System.out.println("VM Event: " + e); + if (e instanceof VMStartEvent) { +// System.out.println("start"); + + } else if (e instanceof ClassPrepareEvent) { + vmClassPrepareEvent((ClassPrepareEvent) e); + + } else if (e instanceof BreakpointEvent) { + vmBreakPointEvent((BreakpointEvent) e); + + } else if (e instanceof StepEvent) { + vmStepEvent(((StepEvent) e)); + + } else if (e instanceof VMDisconnectEvent) { + stopDebug(); + + } else if (e instanceof VMDeathEvent) { + started = false; + editor.statusEmpty(); + } + } + } + + private void vmClassPrepareEvent(ClassPrepareEvent ce) { + ReferenceType rt = ce.referenceType(); + currentThread = ce.thread(); + paused = true; // for now we're paused + + if (rt.name().equals(mainClassName)) { + //printType(rt); + mainClass = rt; + classes.add(rt); +// log("main class load: " + rt.name()); + started = true; // now that main class is loaded, we're started + } else { + classes.add(rt); // save loaded classes +// log("class load: {0}" + rt.name()); + } + + // notify listeners + for (ClassLoadListener listener : classLoadListeners) { + if (listener != null) { + listener.classLoaded(rt); + } + } + paused = false; // resuming now + runtime.vm().resume(); + } + + private void vmBreakPointEvent(BreakpointEvent be) { + currentThread = be.thread(); // save this thread + updateVariableInspector(currentThread); // this is already on the EDT + final LineID newCurrentLine = locationToLineID(be.location()); + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { + editor.setCurrentLine(newCurrentLine); + editor.deactivateStep(); + editor.deactivateContinue(); + } + }); + + // hit a breakpoint during a step, need to cancel the step. + if (requestedStep != null) { + runtime.vm().eventRequestManager().deleteEventRequest(requestedStep); + requestedStep = null; + } + + // fix canvas update issue + // TODO: is this a good solution? + resumeOtherThreads(currentThread); + + paused = true; + editor.statusHalted(); + } + + private void vmStepEvent(StepEvent se) { + currentThread = se.thread(); + + //printSourceLocation(currentThread); + updateVariableInspector(currentThread); // this is already on the EDT + final LineID newCurrentLine = locationToLineID(se.location()); + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + editor.setCurrentLine(newCurrentLine); + editor.deactivateStep(); + editor.deactivateContinue(); + } + }); + + // delete the steprequest that triggered this step so new ones can be placed (only one per thread) + EventRequestManager mgr = runtime.vm().eventRequestManager(); + mgr.deleteEventRequest(se.request()); + requestedStep = null; // mark that there is no step request pending + paused = true; + editor.statusHalted(); + + // disallow stepping into invisible lines + if (!locationIsVisible(se.location())) { + // TODO: this leads to stepping, should it run on the EDT? + javax.swing.SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + stepOutIntoViewOrContinue(); + } + }); + } + } + + + @Override public synchronized void continueDebug() { + editor.activateContinue(); + inspector.lock(); + //editor.clearSelection(); + //clearHighlight(); + editor.clearCurrentLine(); + if (!isStarted()) { + startDebug(); + } else if (isPaused()) { + runtime.vm().resume(); + paused = false; + editor.statusBusy(); + } + } + + + + @Override protected void step(int stepDepth) { + if (!isStarted()) { + startDebug(); + } else if (isPaused()) { + inspector.lock(); + editor.activateStep(); + + // use global to mark that there is a step request pending + requestedStep = runtime.vm().eventRequestManager().createStepRequest(currentThread, StepRequest.STEP_LINE, stepDepth); + requestedStep.addCountFilter(1); // valid for one step only + requestedStep.enable(); + paused = false; + runtime.vm().resume(); + editor.statusBusy(); + } + } + + + + @Override public synchronized void stopDebug() { + inspector.lock(); + if (runtime != null) { + + for (LineBreakpoint bp : breakpoints) { + bp.detach(); + } + + runtime.close(); + runtime = null; + //build = null; + classes.clear(); + // need to clear highlight here because, VMDisconnectedEvent seems to be unreliable. TODO: likely synchronization problem + editor.clearCurrentLine(); + } + stopTrackingLineChanges(); + started = false; + + // editor.deactivateDebug(); + editor.deactivateContinue(); + editor.deactivateStep(); + + editor.statusEmpty(); + } + + + /** + * Watch all classes ({@value sketchClassName}) variable + */ + private void addClassWatch(VirtualMachine vm) { + EventRequestManager erm = vm.eventRequestManager(); + ClassPrepareRequest classPrepareRequest = erm.createClassPrepareRequest(); + classPrepareRequest.addClassFilter(mainClassName); + classPrepareRequest.setEnabled(true); + } + + @Override + public VirtualMachine vm() { + if (runtime != null) { + return runtime.vm(); + } + return null; + } + + @Override public synchronized boolean isStarted() { + return started && runtime != null && runtime.vm() != null; + } + + /** + * Get the breakpoint on a certain line, if set. + * + * @param line the line to get the breakpoint from + * @return the breakpoint, or null if no breakpoint is set on the specified + * line. + */ + LineBreakpoint breakpointOnLine(LineID line) { + for (LineBreakpoint bp : breakpoints) { + if (bp.isOnLine(line)) { + return bp; + } + } + return null; + } + + synchronized public void toggleBreakpoint(int lineIdx) { + LineID line = editor.getLineIDInCurrentTab(lineIdx); + int index = line.lineIdx(); + if (hasBreakpoint(line)) { + removeBreakpoint(index); + } else { + // Make sure the line contains actual code before setting the break + // https://github.com/processing/processing/issues/3765 + if (editor.getLineText(index).trim().length() != 0) { + setBreakpoint(index); + } + } + } + + /** + * Set a breakpoint on a line in the current tab. + * + * @param lineIdx the line index (0-based) of the current tab to set the + * breakpoint on + */ + synchronized void setBreakpoint(int lineIdx) { + setBreakpoint(editor.getLineIDInCurrentTab(lineIdx)); + } + + synchronized public void setBreakpoint(LineID line) { + // do nothing if we are kinda busy + if (isStarted() && !isPaused()) { + return; + } + // do nothing if there already is a breakpoint on this line + if (hasBreakpoint(line)) { + return; + } + breakpoints.add(new AndroidLineBreakpoint(line, this)); + } + + /** + * Remove a breakpoint from the current line (if set). + */ + synchronized void removeBreakpoint() { + removeBreakpoint(editor.getCurrentLineID().lineIdx()); + } + + /** + * Remove a breakpoint from a line in the current tab. + * + * @param lineIdx the line index (0-based) in the current tab to remove the + * breakpoint from + */ + void removeBreakpoint(int lineIdx) { + // do nothing if we are kinda busy + if (isBusy()) { + return; + } + + LineBreakpoint bp = breakpointOnLine(editor.getLineIDInCurrentTab(lineIdx)); + if (bp != null) { + bp.remove(); + breakpoints.remove(bp); + } + } + + public String getPackageName() { + return pkgName; + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidEditor.java b/processing/mode/src/processing/mode/android/AndroidEditor.java new file mode 100644 index 000000000..d45cb4ad9 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidEditor.java @@ -0,0 +1,828 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2009-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import processing.app.Base; +import processing.app.Mode; +import processing.app.Language; +import processing.app.Platform; +import processing.app.Settings; +import processing.app.SketchException; +import processing.app.tools.Tool; +import processing.app.ui.EditorException; +import processing.app.ui.EditorState; +import processing.app.ui.EditorToolbar; +import processing.app.ui.Toolkit; +import processing.mode.java.JavaEditor; +import processing.mode.java.debug.Debugger; +import processing.mode.java.debug.LineID; +import processing.mode.java.preproc.PdePreprocessor; + +import javax.swing.*; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.event.MenuEvent; +import javax.swing.event.MenuListener; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.TimerTask; + + +@SuppressWarnings("serial") +public class AndroidEditor extends JavaEditor { + // Component selected by default + static public final String DEFAULT_COMPONENT = "app"; + + private JMenu androidMenu; + + private int appComponent; + + protected JMenu debugMenu; + private AndroidDebugger debugger; + + private Settings settings; + private AndroidMode androidMode; + + private List androidTools; + + private JCheckBoxMenuItem fragmentItem; + private JCheckBoxMenuItem wallpaperItem; + private JCheckBoxMenuItem watchfaceItem; + private JCheckBoxMenuItem vrItem; + private JCheckBoxMenuItem arItem; + + protected AndroidEditor(Base base, String path, EditorState state, + Mode mode) throws EditorException { + super(base, path, state, mode); + + androidMode = (AndroidMode) mode; + androidMode.resetUserSelection(); + androidMode.checkSDK(this); + + + androidTools = loadAndroidTools(); + addToolsToMenu(); + + loadModeSettings(); + } + +// @Override +// public PdePreprocessor createPreprocessor(final String sketchName) { +// return new AndroidPreprocessor(sketchName); +// } + + + public EditorToolbar createToolbar() { + return new AndroidToolbar(this, base); + } + + + /* + // Not for now, it is unclear if the package name should be reset after save + // as, i.e.: sketch_1 -> sketch_2 ... + @Override + public boolean handleSaveAs() { + boolean saved = super.handleSaveAs(); + if (saved) { + // Reset the manifest so package name and versions are blank + androidMode.resetManifest(sketch, appComponent); + } + return saved; + } + */ + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public JMenu buildFileMenu() { + String exportPackageTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT_PACKAGE); + JMenuItem exportPackage = Toolkit.newJMenuItemShift(exportPackageTitle, 'X'); + exportPackage.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleExportPackage(); + } + }); + + + String exportBundleTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT_BUNDLE); + JMenuItem exportBundle = Toolkit.newJMenuItemShift(exportBundleTitle, 'B'); + exportBundle.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleExportBundle(); + } + }); + + + String exportProjectTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT_PROJECT); + JMenuItem exportProject = Toolkit.newJMenuItemShift(exportProjectTitle, 'E'); + exportProject.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleExportProject(); + } + }); + + return buildFileMenu(new JMenuItem[] {exportPackage, exportBundle, exportProject}); + } + + + public JMenu buildSketchMenu() { + JMenuItem runItem = Toolkit.newJMenuItem(AndroidToolbar.getTitle(AndroidToolbar.RUN_ON_DEVICE), 'R'); + runItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleRunDevice(); + } + }); + + JMenuItem presentItem = Toolkit.newJMenuItemShift(AndroidToolbar.getTitle(AndroidToolbar.RUN_IN_EMULATOR), 'R'); + presentItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleRunEmulator(); + } + }); + + JMenuItem stopItem = new JMenuItem(AndroidToolbar.getTitle(AndroidToolbar.STOP)); + stopItem.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + handleStop(); + } + }); + return buildSketchMenu(new JMenuItem[] { buildDebugMenu(), runItem, presentItem, stopItem }); +// return buildSketchMenu(new JMenuItem[] { runItem, presentItem, stopItem }); + } + + + public JMenu buildModeMenu() { + super.buildModeMenu(); + + androidMenu = new JMenu(AndroidMode.getTextString("menu.android")); + JMenuItem item; + + item = new JMenuItem(AndroidMode.getTextString("menu.android.sketch_permissions")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + new Permissions(sketch, appComponent, androidMode.getFolder()); + } + }); + androidMenu.add(item); + + androidMenu.addSeparator(); + + fragmentItem = new JCheckBoxMenuItem(AndroidMode.getTextString("menu.android.app")); + wallpaperItem = new JCheckBoxMenuItem(AndroidMode.getTextString("menu.android.wallpaper")); + watchfaceItem = new JCheckBoxMenuItem(AndroidMode.getTextString("menu.android.watch_face")); + vrItem = new JCheckBoxMenuItem(AndroidMode.getTextString("menu.android.vr")); + arItem = new JCheckBoxMenuItem(AndroidMode.getTextString("menu.android.ar")); + + fragmentItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + fragmentItem.setState(true); + wallpaperItem.setState(false); + watchfaceItem.setSelected(false); + vrItem.setSelected(false); + arItem.setSelected(false); + setAppComponent(AndroidBuild.APP); + } + }); + wallpaperItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + fragmentItem.setState(false); + wallpaperItem.setState(true); + watchfaceItem.setSelected(false); + vrItem.setSelected(false); + arItem.setSelected(false); + setAppComponent(AndroidBuild.WALLPAPER); + } + }); + watchfaceItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + fragmentItem.setState(false); + wallpaperItem.setState(false); + watchfaceItem.setSelected(true); + vrItem.setSelected(false); + arItem.setSelected(false); + setAppComponent(AndroidBuild.WATCHFACE); + } + }); + vrItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + fragmentItem.setState(false); + wallpaperItem.setState(false); + watchfaceItem.setSelected(false); + vrItem.setSelected(true); + arItem.setSelected(false); + setAppComponent(AndroidBuild.VR); + } + }); + arItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + fragmentItem.setState(false); + wallpaperItem.setState(false); + watchfaceItem.setSelected(false); + vrItem.setSelected(false); + arItem.setSelected(true); + setAppComponent(AndroidBuild.AR); + } + }); + + fragmentItem.setState(false); + wallpaperItem.setState(false); + watchfaceItem.setSelected(false); + vrItem.setSelected(false); + arItem.setSelected(false); + + androidMenu.add(fragmentItem); + androidMenu.add(wallpaperItem); + androidMenu.add(watchfaceItem); + androidMenu.add(vrItem); + androidMenu.add(arItem); + + androidMenu.addSeparator(); + + final JMenu devicesMenu = new JMenu(AndroidMode.getTextString("menu.android.devices")); + + JMenuItem noDevicesItem = new JMenuItem(AndroidMode.getTextString("menu.android.devices.no_connected_devices")); + noDevicesItem.setEnabled(false); + devicesMenu.add(noDevicesItem); + androidMenu.add(devicesMenu); + + // Update the device list only when the Android menu is selected. + androidMenu.addMenuListener(new MenuListener() { + UpdateDeviceListTask task; + java.util.Timer timer; + + @Override + public void menuSelected(MenuEvent e) { + task = new UpdateDeviceListTask(devicesMenu); + timer = new java.util.Timer(); + timer.schedule(task, 400, 3000); + } + + @Override + public void menuDeselected(MenuEvent e) { + timer.cancel(); + } + + @Override + public void menuCanceled(MenuEvent e) { + timer.cancel(); + } + }); + + androidMenu.addSeparator(); + + return androidMenu; + } + + private JMenu buildDebugMenu() { + initDebugger(); + debugMenu = new JMenu(Language.text("menu.debug")); + debugger.populateMenu(debugMenu); + return debugMenu; + } + + + private void setAppComponent(int comp) { + if (appComponent != comp) { + appComponent = comp; + + if (appComponent == AndroidBuild.APP) { + settings.set("component", "app"); + } else if (appComponent == AndroidBuild.WALLPAPER) { + settings.set("component", "wallpaper"); + } else if (appComponent == AndroidBuild.WATCHFACE) { + settings.set("component", "watchface"); + } else if (appComponent == AndroidBuild.VR) { + settings.set("component", "vr"); + } else if (appComponent == AndroidBuild.AR) { + settings.set("component", "ar"); + } + settings.save(); + androidMode.resetManifest(sketch, appComponent); + androidMode.showSelectComponentMessage(comp); + } + } + + + /** + * Uses the main help menu, and adds a few extra options. If/when there's + * Android-specific documentation, we'll switch to that. + */ + public JMenu buildHelpMenu() { + JMenu menu = super.buildHelpMenu(); + JMenuItem item; + + menu.addSeparator(); + + item = new JMenuItem(AndroidMode.getTextString("menu.help.processing_for_android_site")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Platform.openURL("https://android.processing.org/"); + } + }); + menu.add(item); + + + item = new JMenuItem(AndroidMode.getTextString("menu.help.android_developer_site")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + Platform.openURL("https://developer.android.com/"); + } + }); + menu.add(item); + + return menu; + } + + + /** override the standard grab reference to just show the java reference */ + public void showReference(String filename) { + File javaReferenceFolder = Platform.getContentFile("modes/java/reference"); + File file = new File(javaReferenceFolder, filename); + Platform.openURL(file.toURI().toString()); + } + + + public void statusError(String what) { + super.statusError(what); + toolbar.deactivateRun(); + } + + + public void sketchStopped() { + deactivateRun(); + statusEmpty(); + } + + + /** + * Build the sketch and run it inside an emulator with the debugger. + */ + public void handleRunEmulator() { + new Thread() { + public void run() { + toolbar.activateRun(); + startIndeterminate(); + prepareRun(); + try { + androidMode.handleRunEmulator(sketch, AndroidEditor.this, AndroidEditor.this); + } catch (SketchException e) { + statusError(e); + } catch (IOException e) { + statusError(e); + } + stopIndeterminate(); + } + }.start(); + } + + + /** + * Build the sketch and run it on a device with the debugger connected. + */ + public void handleRunDevice() { + new Thread() { + public void run() { + toolbar.activateRun(); + startIndeterminate(); + prepareRun(); + try { + androidMode.handleRunDevice(sketch, AndroidEditor.this, AndroidEditor.this); + } catch (SketchException e) { + statusError(e); + } catch (IOException e) { + statusError(e); + } + stopIndeterminate(); + } + }.start(); + } + + + public void handleStop() { + /* + if (debugger.isStarted()) { + debugger.stopDebug(); + + } else { + toolbar.activateStop(); + androidMode.handleStop(this); + toolbar.deactivateStop(); + toolbar.deactivateRun(); + + // focus the PDE again after quitting presentation mode [toxi 030903] + toFront(); + } + */ + toolbar.activateStop(); + androidMode.handleStop(this); + toolbar.deactivateStop(); + toolbar.deactivateRun(); + + // focus the PDE again after quitting presentation mode [toxi 030903] + toFront(); + } + + @Override + public AndroidDebugger getDebugger() { + return debugger; + } + + +// @Override protected void deactivateDebug() { +// super.deactivateDebug(); +// } + + @Override public void activateContinue() { + ((AndroidToolbar) toolbar).activateContinue(); + } + + @Override public void deactivateContinue() { + ((AndroidToolbar) toolbar).deactivateContinue(); + } + + @Override public void activateStep() { + ((AndroidToolbar) toolbar).activateStep(); + } + + @Override public void deactivateStep() { + ((AndroidToolbar) toolbar).deactivateStep(); + } + + + + @Override + public void toggleDebug() { + super.toggleDebug(); + // make the unused inspector invisible + super.debugger.dispose(); + debugger.toggleDebug(); + } + + @Override + public void toggleBreakpoint(int lineIndex) { + debugger.toggleBreakpoint(lineIndex); + } + + @Override + public LineID getCurrentLineID() { + return super.getCurrentLineID(); + } + + /** + * Create a release build of the sketch and have its apk files ready. + * If users want a debug build, they can do that from the command line. + */ + public void handleExportProject() { + if (handleExportCheckModified()) { + new Thread() { + public void run() { + ((AndroidToolbar) toolbar).activateExport(); + startIndeterminate(); + statusNotice(AndroidMode.getTextString("android_editor.status.exporting_project")); + AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent); + try { + File exportFolder = build.exportProject(); + if (exportFolder != null) { + Platform.openFolder(exportFolder); + statusNotice(AndroidMode.getTextString("android_editor.status.project_export_completed")); + } else { + statusError(AndroidMode.getTextString("android_editor.status.project_export_failed")); + } + } catch (IOException e) { + statusError(e); + } catch (SketchException e) { + statusError(e); + } + stopIndeterminate(); + ((AndroidToolbar)toolbar).deactivateExport(); + } + }.start(); + } + } + + /** + * Create a release package of the sketch + */ + public void handleExportPackage() { + if (androidMode.checkPackageName(sketch, appComponent) && + androidMode.checkAppIcons(sketch, appComponent) && handleExportCheckModified()) { + new KeyStoreManager(this, KeyStoreManager.PACKAGE); + } + } + + public void startExportPackage(final String keyStorePassword) { + new Thread() { + public void run() { + startIndeterminate(); + statusNotice(AndroidMode.getTextString("android_editor.status.exporting_package")); + AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent); + try { + File projectFolder = build.exportPackage(keyStorePassword); + if (projectFolder != null) { + statusNotice(AndroidMode.getTextString("android_editor.status.package_export_completed")); + Platform.openFolder(projectFolder); + } else { + statusError(AndroidMode.getTextString("android_editor.status.package_export_failed")); + } + } catch (IOException e) { + statusError(e); + } catch (SketchException e) { + statusError(e); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + stopIndeterminate(); + } + }.start(); + } + + + /** + * Create a release bundle of the sketch + */ + public void handleExportBundle() { + if (androidMode.checkPackageName(sketch, appComponent) && + androidMode.checkAppIcons(sketch, appComponent) && handleExportCheckModified()) { + new KeyStoreManager(this, KeyStoreManager.BUNDLE); + } + } + + public void startExportBundle(final String keyStorePassword) { + new Thread() { + public void run() { + startIndeterminate(); + statusNotice(AndroidMode.getTextString("android_editor.status.exporting_bundle")); + AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent); + try { + File projectFolder = build.exportBundle(keyStorePassword); + if (projectFolder != null) { + statusNotice(AndroidMode.getTextString("android_editor.status.bundle_export_completed")); + Platform.openFolder(projectFolder); + } else { + statusError(AndroidMode.getTextString("android_editor.status.bundle_export_failed")); + } + } catch (IOException e) { + statusError(e); + } catch (SketchException e) { + statusError(e); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + stopIndeterminate(); + } + }.start(); + } + + + public int getAppComponent() { + return appComponent; + } + + + private void loadModeSettings() { + File sketchProps = new File(sketch.getCodeFolder(), "sketch.properties"); + try { + settings = new Settings(sketchProps); + boolean save = false; + String component; + if (!sketchProps.exists()) { + component = DEFAULT_COMPONENT; + settings.set("component", component); + save = true; + } else { + component = settings.get("component"); + if (component == null) { + component = DEFAULT_COMPONENT; + settings.set("component", component); + save = true; + } + } + if (save) settings.save(); + + if (component.equals("app")) { + appComponent = AndroidBuild.APP; + fragmentItem.setState(true); + } else if (component.equals("wallpaper")) { + appComponent = AndroidBuild.WALLPAPER; + wallpaperItem.setState(true); + } else if (component.equals("watchface")) { + appComponent = AndroidBuild.WATCHFACE; + watchfaceItem.setState(true); + } else if (component.equals("vr")) { + appComponent = AndroidBuild.VR; + vrItem.setState(true); + } else if (component.equals("ar")) { + appComponent = AndroidBuild.AR; + arItem.setState(true); + } + + androidMode.initManifest(sketch, appComponent); + } catch (IOException e) { + System.err.println(AndroidMode.getTextString("android_editor.error.cannot_create_sketch_properties", sketchProps, e.getMessage())); + } + } + + private List loadAndroidTools() { + // This gets called before assigning mode to androidMode... + ArrayList outgoing = new ArrayList(); + + File folder = new File(androidMode.getFolder(), "tools"); + String[] list = folder.list(); + if (list == null) { + return outgoing; + } + + Arrays.sort(list, String.CASE_INSENSITIVE_ORDER); + for (String name : list) { + if (name.charAt(0) == '.') { + continue; + } + + File toolPath = new File(folder, name); + if (toolPath.isDirectory()) { + File jarPath = new File(toolPath, "tool" + File.separator + name + ".jar"); + if (jarPath.exists()) { + try { + AndroidTool tool = new AndroidTool(toolPath, androidMode); + tool.init(base); + outgoing.add(tool); + } catch (Throwable e) { + e.printStackTrace(); + } + } + } + } + + return outgoing; + } + + private void addToolsToMenu() { + JMenuItem item; + + for (final Tool tool : androidTools) { + item = new JMenuItem(AndroidMode.getTextString(tool.getMenuTitle())); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + tool.run(); + } + }); + androidMenu.add(item); + } + +// item = new JMenuItem("AVD Manager"); +// item.addActionListener(new ActionListener() { +// public void actionPerformed(ActionEvent e) { +// File file = androidMode.getSDK().getAndroidTool(); +// PApplet.exec(new String[] { file.getAbsolutePath(), "avd" }); +// } +// }); +// menu.add(item); + + item = new JMenuItem(AndroidMode.getTextString("menu.android.reset_adb")); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { +// editor.statusNotice("Resetting the Android Debug Bridge server."); + final Devices devices = Devices.getInstance(); + devices.killAdbServer(); + devices.startAdbServer(); + } + }); + androidMenu.add(item); + } + + private void initDebugger() { + debugger = new AndroidDebugger(this, androidMode); + // Set saved breakpoints when sketch is opened for the first time +// for (LineID lineID : stripBreakpointComments()) { +// debugger.setBreakpoint(lineID); +// } + super.debugger = debugger; + + } + + class UpdateDeviceListTask extends TimerTask { + + private JMenu deviceMenu; + + public UpdateDeviceListTask(JMenu deviceMenu) { + this.deviceMenu = deviceMenu; + } + + private Device selectFirstDevice(java.util.List deviceList) { + if (0 < deviceList.size()) return deviceList.get(0); + return null; + } + + @Override + public void run() { + if (androidMode == null || androidMode.getSDK() == null) return; + + final Devices devices = Devices.getInstance(); + + if (appComponent == AndroidBuild.WATCHFACE) { + devices.enableBluetoothDebugging(); + } + + java.util.List deviceList = devices.findMultiple(false); + Device selectedDevice = devices.getSelectedDevice(); + + if (deviceList.size() == 0) { + if (0 < deviceMenu.getItemCount()) { + deviceMenu.removeAll(); + JMenuItem noDevicesItem = new JMenuItem(AndroidMode.getTextString("menu.android.devices.no_connected_devices")); + noDevicesItem.setEnabled(false); + deviceMenu.add(noDevicesItem); + } + devices.setSelectedDevice(null); + } else { + deviceMenu.removeAll(); + + if (selectedDevice == null) { + selectedDevice = selectFirstDevice(deviceList); + devices.setSelectedDevice(selectedDevice); + } else { + // check if selected device is still connected + boolean found = false; + for (Device device : deviceList) { + if (device.equals(selectedDevice)) { + found = true; + break; + } + } + + if (!found) { + selectedDevice = selectFirstDevice(deviceList); + devices.setSelectedDevice(selectedDevice); + } + } + + for (final Device device : deviceList) { + final JCheckBoxMenuItem deviceItem = new JCheckBoxMenuItem(device.getName()); + deviceItem.setEnabled(true); + + if (device.equals(selectedDevice)) deviceItem.setState(true); + + // prevent checkboxmenuitem automatic state changing onclick + deviceItem.addChangeListener(new ChangeListener() { + @Override + public void stateChanged(ChangeEvent e) { + if (device.equals(devices.getSelectedDevice())) deviceItem.setState(true); + else deviceItem.setState(false); + } + }); + + deviceItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + devices.setSelectedDevice(device); + + for (int i = 0; i < deviceMenu.getItemCount(); i++) { + ((JCheckBoxMenuItem) deviceMenu.getItem(i)).setState(false); + } + + deviceItem.setState(true); + } + }); + + deviceMenu.add(deviceItem); + } + } + } + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidKeyStore.java b/processing/mode/src/processing/mode/android/AndroidKeyStore.java new file mode 100644 index 000000000..711678f56 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidKeyStore.java @@ -0,0 +1,120 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2014-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import processing.app.Messages; +import processing.app.exec.ProcessHelper; +import processing.app.exec.ProcessResult; +import processing.core.PApplet; +import java.io.File; + +/** + * Class handling the keystore where the users can store the credentials for + * their apps. + */ +public class AndroidKeyStore { + public static final String ALIAS_STRING = "p5android-key"; + public static final int KEY_VALIDITY_YEARS = 25; + public static final String KEYSTORE_FILE_NAME = "processing-upload-keystore.jks"; + + public static File getKeyStore() { + return getKeyStore(KEYSTORE_FILE_NAME); + } + + public static File getKeyStore(String name) { + File keyStore = getKeyStoreLocation(name); + if (!keyStore.exists()) return null; + return keyStore; + } + + public static File getKeyStoreLocation(String name) { + File sketchbookFolder = processing.app.Base.getSketchbookFolder(); + File androidFolder = new File(sketchbookFolder, "android"); + File keyStoreFolder = new File(androidFolder, "keystore"); + if (!keyStoreFolder.exists()) { + boolean result = keyStoreFolder.mkdirs(); + + if (!result) { + Messages.showWarning(AndroidMode.getTextString("android_keystore.warn.cannot_create_folders.title"), + AndroidMode.getTextString("android_keystore.warn.cannot_create_folders.body")); + return null; + } + } + + File keyStore = new File(keyStoreFolder, name); + return keyStore; + } + + public static void generateKeyStore(String password, + String commonName, String organizationalUnit, + String organizationName, String locality, + String state, String country) throws Exception { + String dnamePlaceholder = "CN=%s, OU=%s, O=%s, L=%s, S=%s, C=%s"; + String dname = String.format(dnamePlaceholder, + parseDnameField(commonName), parseDnameField(organizationalUnit), parseDnameField(organizationName), + parseDnameField(locality), parseDnameField(state), parseDnameField(country)); + + ProcessHelper ph = new ProcessHelper(new String[] { + "keytool", "-genkey", + "-keystore", getKeyStoreLocation(KEYSTORE_FILE_NAME).getAbsolutePath(), + "-alias", ALIAS_STRING, + "-keyalg", "RSA", + "-keysize", "2048", + "-validity", Integer.toString(KEY_VALIDITY_YEARS * 365), + "-keypass", password, + "-storepass", password, + "-dname", dname + }); + + try { + ProcessResult result = ph.execute(); + if (result.succeeded()) { + if (getKeyStore() == null) { + Messages.showWarning(AndroidMode.getTextString("android_keystore.warn.cannot_find_keystore.title"), + AndroidMode.getTextString("android_keystore.warn.cannot_find_keystore.body")); + } + } else { + String[] lines = PApplet.split(result.getStderr(), '\n'); + System.err.println(AndroidMode.getTextString("android_keystore.error.cannot_create_keystore")); + for (String line: lines) { + System.err.println(line); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static boolean resetKeyStore() { + File keyStore = getKeyStore(); + if (keyStore == null) return true; + + File keyStoreBackup = getKeyStoreLocation(KEYSTORE_FILE_NAME + "-" + AndroidMode.getDateStamp()); + if (!keyStore.renameTo(keyStoreBackup)) return false; + return true; + } + + private static String parseDnameField(String content) { + if (content == null || content.length() == 0) return "Unknown"; + else return content; + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidLineBreakpoint.java b/processing/mode/src/processing/mode/android/AndroidLineBreakpoint.java new file mode 100644 index 000000000..3716826b1 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidLineBreakpoint.java @@ -0,0 +1,59 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2018-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import com.sun.jdi.ReferenceType; +import processing.mode.java.debug.Debugger; +import processing.mode.java.debug.LineBreakpoint; +import processing.mode.java.debug.LineID; + +public class AndroidLineBreakpoint extends LineBreakpoint { + private boolean alreadyAdded; + + public AndroidLineBreakpoint(LineID line, Debugger dbg) { + super(line, dbg); + } + + public AndroidLineBreakpoint(int lineIdx, Debugger dbg) { + super(lineIdx, dbg); + } + + @Override public void classLoaded(ReferenceType theClass) { + if (!isAttached()) { + addPackageName(); + // try to attach + attach(theClass); + } + } + + + /** + * Add package name to the class name. Needed to match + * the logical class name to the VM (Physical) class name + */ + private void addPackageName() { + if (!alreadyAdded) { + className = ((AndroidDebugger) dbg).getPackageName() + "." + className; + alreadyAdded = !alreadyAdded; + } + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidMode.java b/processing/mode/src/processing/mode/android/AndroidMode.java new file mode 100644 index 000000000..1d341c164 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidMode.java @@ -0,0 +1,441 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2011-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import processing.app.Base; +import processing.app.Library; +import processing.app.Messages; +import processing.app.Platform; +import processing.app.RunnerListener; +import processing.app.Sketch; +import processing.app.SketchException; +import processing.app.ui.Editor; +import processing.app.ui.EditorException; +import processing.app.ui.EditorState; +import processing.core.PApplet; +import processing.mode.android.AndroidSDK.CancelException; +import processing.mode.java.JavaMode; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Future; + +/** + * Programming mode to create and run Processing sketches on Android devices. + */ +public class AndroidMode extends JavaMode { + private AndroidSDK sdk; + private File coreZipLocation; + private AndroidRunner runner; + + private boolean showWatchFaceDebugMessage = true; + private boolean showWatchFaceSelectMessage = true; + private boolean showWallpaperSelectMessage = true; + + private boolean checkingSDK = false; + private boolean userCancelledSDKSearch = false; + + // Using this temporarily until support for mode translations is finalized in the Processing app + private static Map textStrings = null; + + private static final String VERSIONS_FILE = "version.properties"; + + private static final String BLUETOOTH_DEBUG_URL = + "https://developer.android.com/training/wearables/get-started/debugging"; + + private static final String DISTRIBUTING_APPS_TUT_URL = + "https://android.processing.org/tutorials/distributing/index.html"; + + public AndroidMode(Base base, File folder) { + super(base, folder); + AndroidBuild.initVersions(getContentFile(VERSIONS_FILE)); + loadTextStrings(); + } + + + @Override + public Editor createEditor(Base base, String path, + EditorState state) throws EditorException { + return new AndroidEditor(base, path, state, this); + } + + + @Override + public String getTitle() { + return "Android"; + } + + + public File[] getKeywordFiles() { + return new File[] { + Platform.getContentFile("modes/java/keywords.txt"), + getContentFile("keywords.txt") + }; + } + + + public File[] getExampleCategoryFolders() { + return new File[] { + new File(examplesFolder, "Basics"), + new File(examplesFolder, "Topics"), + new File(examplesFolder, "Demos"), + new File(examplesFolder, "Sensors") + }; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + /** @return null so that it doesn't try to pass along the desktop version of core.jar */ + public Library getCoreLibrary() { + return null; + } + + + protected File getCoreZipLocation() { + if (coreZipLocation == null) { + /* + // for debugging only, check to see if this is an svn checkout + File debugFile = new File("../../../android/core.zip"); + if (!debugFile.exists() && Base.isMacOS()) { + // current path might be inside Processing.app, so need to go much higher + debugFile = new File("../../../../../../../android/core.zip"); + } + if (debugFile.exists()) { + System.out.println("Using version of core.zip from local SVN checkout."); +// return debugFile; + coreZipLocation = debugFile; + } + */ + + // otherwise do the usual + // return new File(base.getSketchbookFolder(), ANDROID_CORE_FILENAME); + coreZipLocation = getContentFile("processing-core.zip"); + } + return coreZipLocation; + } + + + public void resetUserSelection() { + userCancelledSDKSearch = false; + } + + + public void checkSDK(Editor editor) { + if (checkingSDK) { + // Some other thread has invoked SDK checking, so wait until the first one + // is done (it might involve downloading the SDK, etc). + while (checkingSDK) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + return; + } + } + } + if (userCancelledSDKSearch) return; + checkingSDK = true; + Throwable tr = null; + if (sdk == null) { + try { + sdk = AndroidSDK.load(true, editor); + if (sdk == null) { + sdk = AndroidSDK.locate(editor, this); + } + } catch (CancelException cancel) { + userCancelledSDKSearch = true; + tr = cancel; + } catch (Exception other) { + tr = other; + } + } + if (sdk == null) { + Messages.showWarning(AndroidMode.getTextString("android_mode.warn.cannot_load_sdk_title"), + AndroidMode.getTextString("android_mode.warn.cannot_load_sdk_body"), tr); + } else { + Devices devices = Devices.getInstance(); + devices.setSDK(sdk); + } + checkingSDK = false; + } + + + public AndroidSDK getSDK() { + return sdk; + } + + + public File getResourcesFolder() { + return new File(getFolder(), "resources"); + } + + + public String getModeJar() { + String modePath = new File(getFolder(), "mode").getAbsolutePath(); + return modePath + File.separator + "AndroidMode.jar"; + } + + + @Override + public String getSearchPath() { + if (sdk == null) { + checkSDK(null); + } + + if (sdk == null) { + Messages.log(AndroidMode.getTextString("android_mode.info.cannot_open_sdk_path")); + return ""; + } + + String coreJarPath = new File(getFolder(), "processing-core.zip").getAbsolutePath(); + return sdk.getAndroidJarPath().getAbsolutePath() + File.pathSeparatorChar + coreJarPath; + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd.HHmm"); + + + static public String getDateStamp() { + return dateFormat.format(new Date()); + } + + + static public String getDateStamp(long stamp) { + return dateFormat.format(new Date(stamp)); + } + + + // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + + + public void handleRunEmulator(Sketch sketch, AndroidEditor editor, + RunnerListener listener) throws SketchException, IOException { + listener.startIndeterminate(); + listener.statusNotice(AndroidMode.getTextString("android_mode.status.starting_project_build")); + AndroidBuild build = new AndroidBuild(sketch, this, editor.getAppComponent()); + + listener.statusNotice(AndroidMode.getTextString("android_mode.status.building_project")); + build.build("debug", ""); + + if (sdk.getEmulatorTool() == null) { + // System.out.println("Try to download the emulator using the SDK Manager..."); + listener.statusNotice(AndroidMode.getTextString("android_mode.status.downloading_emulator")); + boolean emulatorInstallationSucceded = sdk.downloadEmuOnDemand(); + if (!emulatorInstallationSucceded) { + SketchException emulatorInstallationErrorException = new SketchException(AndroidMode.getTextString("android_mode.error.emulator_installation_failed")); + emulatorInstallationErrorException.hideStackTrace(); + throw emulatorInstallationErrorException; + } else { + System.out.println(AndroidMode.getTextString("android_mode.status.downloading_emulator_successful")); + } + } + + boolean avd = AVD.ensureProperAVD(editor, this, sdk, build.isWear()); + if (!avd) { + SketchException se = + new SketchException(AndroidMode.getTextString("android_mode.error.cannot_create_avd")); + se.hideStackTrace(); + throw se; + } + + int comp = build.getAppComponent(); + Future emu = Devices.getInstance().getEmulator(build.isWear()); + runner = new AndroidRunner(build, listener); + runner.launch(emu, comp, true); + } + + + public void handleRunDevice(Sketch sketch, AndroidEditor editor, + RunnerListener listener) + throws SketchException, IOException { + + final Devices devices = Devices.getInstance(); + java.util.List deviceList = devices.findMultiple(false); + if (deviceList.size() == 0) { + Messages.showWarning(AndroidMode.getTextString("android_mode.dialog.no_devices_found_title"), + AndroidMode.getTextString("android_mode.dialog.no_devices_found_body")); + listener.statusError(AndroidMode.getTextString("android_mode.status.no_devices_found")); + return; + } + + listener.startIndeterminate(); + listener.statusNotice(AndroidMode.getTextString("android_mode.status.starting_project_build")); + AndroidBuild build = new AndroidBuild(sketch, this, editor.getAppComponent()); + + listener.statusNotice(AndroidMode.getTextString("android_mode.status.building_project")); + File projectFolder = build.build("debug", ""); + if (projectFolder == null) { + listener.statusError(AndroidMode.getTextString("android_mode.status.project_build_failed")); + return; + } + + int comp = build.getAppComponent(); + Future dev = Devices.getInstance().getHardware(); + runner = new AndroidRunner(build, listener); + if (runner.launch(dev, comp, false)) { + showPostBuildMessage(comp); + } + } + + + public void showSelectComponentMessage(int appComp) { + if (showWatchFaceDebugMessage && appComp == AndroidBuild.WATCHFACE) { + AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.watchface_debug_title"), + AndroidMode.getTextString("android_mode.dialog.watchface_debug_body", BLUETOOTH_DEBUG_URL)); + showWatchFaceDebugMessage = false; + } + } + + + public void showPostBuildMessage(int appComp) { + if (showWallpaperSelectMessage && appComp == AndroidBuild.WALLPAPER) { + AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.wallpaper_installed_title"), + AndroidMode.getTextString("android_mode.dialog.wallpaper_installed_body")); + showWallpaperSelectMessage = false; + } + if (showWatchFaceSelectMessage && appComp == AndroidBuild.WATCHFACE) { + AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.watchface_installed_title"), + AndroidMode.getTextString("android_mode.dialog.watchface_installed_body")); + showWatchFaceSelectMessage = false; + } + } + + + public void handleStop(RunnerListener listener) { + listener.statusNotice(""); + listener.stopIndeterminate(); + +// if (runtime != null) { +// runtime.close(); // kills the window +// runtime = null; // will this help? +// } + if (runner != null) { + runner.close(); + runner = null; + } + } + + + public boolean checkPackageName(Sketch sketch, int comp) { + Manifest manifest = new Manifest(sketch, comp, getFolder(), false); + String defName = Manifest.BASE_PACKAGE + "." + sketch.getName().toLowerCase(); + String name = manifest.getPackageName(); + if (name.toLowerCase().equals(defName.toLowerCase())) { + // The user did not set the package name, show error and stop + AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_export_package_title"), + AndroidMode.getTextString("android_mode.dialog.cannot_export_package_body", DISTRIBUTING_APPS_TUT_URL)); + return false; + } + return true; + } + + + public boolean checkAppIcons(Sketch sketch, int comp) { + File sketchFolder = sketch.getFolder(); + + File[] launcherIcons = AndroidUtil.getFileList(sketchFolder, AndroidBuild.SKETCH_LAUNCHER_ICONS, + AndroidBuild.SKETCH_OLD_LAUNCHER_ICONS); + boolean allFilesExist = AndroidUtil.allFilesExists(launcherIcons); + + if (comp == AndroidBuild.WATCHFACE) { + // Additional preview icons are needed for watch faces + File[] watchFaceIcons = AndroidUtil.getFileList(sketchFolder, AndroidBuild.SKETCH_WATCHFACE_ICONS); + allFilesExist &= AndroidUtil.allFilesExists(watchFaceIcons); + } + + if (!allFilesExist) { + // The user did not set custom icons, show error and stop + AndroidUtil.showMessage(AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_title"), + AndroidMode.getTextString("android_mode.dialog.cannot_use_default_icons_body", DISTRIBUTING_APPS_TUT_URL)); + return false; + } + return true; + } + + + public void initManifest(Sketch sketch, int comp) { + new Manifest(sketch, comp, getFolder(), false); + } + + + public void resetManifest(Sketch sketch, int comp) { + new Manifest(sketch, comp, getFolder(), true); + } + + private void loadTextStrings() { + String baseFilename = "languages/mode.properties"; + File modeBaseFile = new File(getFolder(), baseFilename); + if (textStrings == null) { + textStrings = new HashMap(); + String[] lines = PApplet.loadStrings(modeBaseFile); + if (lines == null) { + throw new NullPointerException("File not found:\n" + modeBaseFile.getAbsolutePath()); + } + //for (String line : lines) { + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if ((line.length() == 0) || + (line.charAt(0) == '#')) continue; + + // this won't properly handle = signs inside in the text + int equals = line.indexOf('='); + if (equals != -1) { + String key = line.substring(0, equals).trim(); + String value = line.substring(equals + 1).trim(); + + value = value.replaceAll("\\\\n", "\n"); + value = value.replaceAll("\\\\'", "'"); + + textStrings.put(key, value); + } + } + } + } + + static public String getTextString(String key) { + if (textStrings.containsKey(key)) { + return textStrings.get(key); + } else { + return key; + } + +// return Language.text(key); + } + + static public String getTextString(String key, Object... arguments) { + String value = textStrings.get(key); + if (value == null) { + return key; + } + return String.format(value, arguments); + } +} + diff --git a/processing/mode/src/processing/mode/android/AndroidRunner.java b/processing/mode/src/processing/mode/android/AndroidRunner.java new file mode 100644 index 000000000..ea22aa93d --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidRunner.java @@ -0,0 +1,298 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2011-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import java.io.IOException; +import java.io.PrintStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.VirtualMachineManager; +import com.sun.jdi.connect.AttachingConnector; +import com.sun.jdi.connect.Connector; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import processing.app.ui.Editor; +import processing.app.Messages; +import processing.app.RunnerListener; +import processing.app.SketchException; +import processing.mode.java.runner.Runner; + +/** + * Launches an app on the device or in the emulator. + */ +public class AndroidRunner implements DeviceListener { + private static final String DEFAULT_PACKAGE_NAME = "processing.android"; + + AndroidBuild build; + RunnerListener listener; + + protected PrintStream sketchErr; + protected PrintStream sketchOut; + + private VirtualMachine vm; + + private boolean isDebugEnabled; + + public AndroidRunner(AndroidBuild build, RunnerListener listener) { + this.build = build; + this.listener = listener; + + if (listener instanceof AndroidEditor) { + isDebugEnabled = ((AndroidEditor) listener).isDebuggerEnabled(); + } + + if (listener instanceof Editor) { + Editor editor = (Editor) listener; + sketchErr = editor.getConsole().getErr(); + sketchOut = editor.getConsole().getOut(); + } else { + sketchErr = System.err; + sketchOut = System.out; + } + } + + + public boolean launch(Future deviceFuture, int comp, boolean emu) { + String devStr = emu ? "emulator" : "device"; + listener.statusNotice(AndroidMode.getTextString("android_runner.status.waiting_for_device", devStr)); + + final Device device = waitForDevice(deviceFuture, listener); + if (device == null || !device.isAlive()) { + listener.statusError(AndroidMode.getTextString("android_runner.status.lost_connection_with_device", devStr)); + // Reset the server, in case that's the problem. Sometimes when + // launching the emulator times out, the device list refuses to update. + final Devices devices = Devices.getInstance(); + devices.killAdbServer(); + return false; + } + + if (comp == AndroidBuild.WATCHFACE && !device.hasFeature("watch")) { + listener.statusError(AndroidMode.getTextString("android_runner.status.cannot_install_sketch")); + Messages.showWarning(AndroidMode.getTextString("android_runner.warn.non_watch_device_title"), + AndroidMode.getTextString("android_runner.warn.non_watch_device_body")); + return false; + } + + if (comp != AndroidBuild.WATCHFACE && device.hasFeature("watch")) { + listener.statusError(AndroidMode.getTextString("android_runner.status.cannot_install_sketch")); + Messages.showWarning(AndroidMode.getTextString("android_runner.warn.watch_device_title"), + AndroidMode.getTextString("android_runner.warn.watch_device_body")); + return false; + } + + device.addListener(this); + device.setPackageName(build.getPackageName()); + listener.statusNotice(AndroidMode.getTextString("android_runner.status.installing_sketch", device.getId())); + // this stopped working with Android SDK tools revision 17 + if (!device.installApp(build, listener)) { + listener.statusError(AndroidMode.getTextString("android_runner.status.lost_connection", devStr)); + final Devices devices = Devices.getInstance(); + devices.killAdbServer(); // see above + return false; + } + + boolean status = false; + if (comp == AndroidBuild.WATCHFACE || comp == AndroidBuild.WALLPAPER) { + if (startSketch(build, device)) { + listener.statusNotice(AndroidMode.getTextString("android_runner.status.sketch_installed") + + (device.isEmulator() ? " " + AndroidMode.getTextString("android_runner.status.in_emulator") : " " + + AndroidMode.getTextString("android_runner.status.on_device")) + "."); + status = true; + } else { + listener.statusError(AndroidMode.getTextString("android_runner.status.cannot_install_sketch")); + } + } else { + listener.statusNotice(AndroidMode.getTextString("android_runner.status.launching_sketch", device.getId())); + if (startSketch(build, device)) { + listener.statusNotice(AndroidMode.getTextString("android_runner.status.sketch_launched") + + (device.isEmulator() ? " " + AndroidMode.getTextString("android_runner.status.in_emulator") : " " + + AndroidMode.getTextString("android_runner.status.on_device")) + "."); + status = true; + } else { + listener.statusError(AndroidMode.getTextString("android_runner.status.cannot_launch_sketch")); + } + } + + // Start Debug if Debugger is enabled + if (isDebugEnabled) { + ((AndroidEditor) listener).getDebugger() + .startDebug(this, device); + } + + listener.stopIndeterminate(); + lastRunDevice = device; + return status; + } + + public VirtualMachine connectVirtualMachine(int port) throws IOException { + String strPort = Integer.toString(port); + AttachingConnector connector = getConnector(); + try { + vm = connect(connector, strPort); + return vm; + } catch (IllegalConnectorArgumentsException e) { + throw new IllegalStateException(e); + } + } + + private AttachingConnector getConnector() { + VirtualMachineManager vmManager = org.eclipse.jdi.Bootstrap.virtualMachineManager(); + for (Connector connector : vmManager.attachingConnectors()) { + if ("com.sun.jdi.SocketAttach".equals(connector.name())) { + return (AttachingConnector) connector; + } + } + throw new IllegalStateException(); + } + + private VirtualMachine connect( + AttachingConnector connector, String port) throws IllegalConnectorArgumentsException, IOException { + Map args = connector + .defaultArguments(); + Connector.Argument pidArgument = args.get("port"); + if (pidArgument == null) { + throw new IllegalStateException(); + } + pidArgument.setValue(port); + + return connector.attach(args); + } + + public VirtualMachine vm() { + return vm; + } + + private volatile Device lastRunDevice = null; + + + // if user asks for 480x320, 320x480, 854x480 etc, then launch like that + // though would need to query the emulator to see if it can do that + + private boolean startSketch(AndroidBuild build, final Device device) { + final String packageName = build.getPackageName(); + try { + if (device.launchApp(packageName, isDebugEnabled)) { + return true; + } + } catch (final Exception e) { + e.printStackTrace(System.err); + } + return false; + } + + + private Device waitForDevice(Future deviceFuture, RunnerListener listener) { + for (int i = 0; i < 120; i++) { + if (listener.isHalted()) { + deviceFuture.cancel(true); + return null; + } + try { + return deviceFuture.get(1, TimeUnit.SECONDS); + } catch (final InterruptedException e) { + listener.statusError("Interrupted."); + return null; + } catch (final ExecutionException e) { + listener.statusError(e); + return null; + } catch (final TimeoutException expected) { + } + } + listener.statusError(AndroidMode.getTextString("android_runner.status.cancel_waiting_for_device")); + return null; + } + + + private static final Pattern LOCATION = + Pattern.compile("\\(([^:]+):(\\d+)\\)"); + private static final Pattern EXCEPTION_PARSER = + Pattern.compile("^\\s*([a-z]+(?:\\.[a-z]+)+)(?:: .+)?$", + Pattern.CASE_INSENSITIVE); + + /** + * Currently figures out the first relevant stack trace line + * by looking for the telltale presence of "processing.android" + * in the package. If the packaging for droid sketches changes, + * this method will have to change too. + */ + public void stackTrace(final List trace) { + final Iterator frames = trace.iterator(); + final String exceptionLine = frames.next(); + + final Matcher m = EXCEPTION_PARSER.matcher(exceptionLine); + if (!m.matches()) { + System.err.println(AndroidMode.getTextString("android_runner.error.cannot_parse_stacktrace")); + System.err.println(exceptionLine); + listener.statusError(AndroidMode.getTextString("android_runner.status.unknwon_exception")); + return; + } + final String exceptionClass = m.group(1); + Runner.handleCommonErrors(exceptionClass, exceptionLine, listener, sketchErr); + + while (frames.hasNext()) { + final String line = frames.next(); + if (line.contains(DEFAULT_PACKAGE_NAME)) { + final Matcher lm = LOCATION.matcher(line); + if (lm.find()) { + final String filename = lm.group(1); + final int lineNumber = Integer.parseInt(lm.group(2)) - 1; + final SketchException rex = + build.placeException(exceptionLine, filename, lineNumber); + listener.statusError(rex == null ? new SketchException(exceptionLine, false) : rex); + return; + } + } + } + } + + + // called by AndroidMode.handleStop()... + public void close() { + if (lastRunDevice != null) { + lastRunDevice.bringLauncherToFront(); + } + + if (vm != null) { + try { + vm.exit(0); + + } catch (com.sun.jdi.VMDisconnectedException vmde) { + // if the vm has disconnected on its own, ignore message + //System.out.println("harmless disconnect " + vmde.getMessage()); + // TODO shouldn't need to do this, need to do more cleanup + } + } + } + + + // sketch stopped on the device + public void sketchStopped() { + listener.stopIndeterminate(); + listener.statusHalt(); + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidSDK.java b/processing/mode/src/processing/mode/android/AndroidSDK.java new file mode 100644 index 000000000..36e7423a1 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidSDK.java @@ -0,0 +1,943 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2013-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import processing.app.Language; +import processing.app.Messages; +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.exec.ProcessHelper; +import processing.app.exec.ProcessResult; +import processing.app.ui.Toolkit; +import processing.core.PApplet; + +import javax.swing.*; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import java.awt.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.nio.file.attribute.PosixFilePermission; + +import java.io.PrintWriter; +import java.util.Set; + +/** + * Class holding all needed references (path, tools, etc) to the SDK used by + * the mode. + */ +class AndroidSDK { + public static boolean adbDisabled = false; + + final static private int FONT_SIZE = Toolkit.zoom(11); + final static private int TEXT_MARGIN = Toolkit.zoom(8); + final static private int TEXT_WIDTH = Toolkit.zoom(300); + + private final File folder; + private final File platforms; + private final File highestPlatform; + private final File androidJar; + private final File platformTools; + private final File buildTools; + private final File cmdlineTools; + private final File avdManager; + private final File sdkManager; + private final File adb; + + private File emulator; + + private static final String PROCESSING_FOR_ANDROID_URL = + "https://android.processing.org/"; + + private static final String WHATS_NEW_URL = + "https://android.processing.org/whatsnew"; + + private static final String SDK_DOWNLOAD_URL = + "https://developer.android.com/studio/index.html#android-studio-downloads"; + + private static final String DRIVER_INSTALL_URL = + "https://developer.android.com/studio/run/oem-usb.html#InstallingDriver"; + + private static final String SYSTEM_32BIT_URL = + "https://askubuntu.com/questions/710426/android-sdk-on-ubuntu-32bit"; + + private static final String SDK_LICENSE_URL = + "https://developer.android.com/studio/terms.html"; + + private static final int NO_ERROR = 0; + private static final int SKIP_ENV_SDK = 1; + private static final int MISSING_SDK = 2; + private static final int INVALID_SDK = 3; + private static int loadError = NO_ERROR; + + public AndroidSDK(File folder) throws BadSDKException, IOException { + this.folder = folder; + if (!folder.exists()) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_sdk_folder", folder)); + } + + cmdlineTools = new File(folder, "cmdline-tools/latest"); + // We need only command line tools, as sdk/tools got deprecated and can't be used with java 17 + if (!cmdlineTools.exists()) { + // Let's be more specific to show the error + File sdkTools = new File(folder, "tools"); + if (sdkTools.exists()) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_cmdtools_folder_found_sdktools", folder)); + } else { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_cmdtools_folder", folder)); + } + } + // If we reached here, that means command line tools exists + // ok to go with the command line tools + + platformTools = new File(folder, "platform-tools"); + if (!platformTools.exists()) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_platform_tools_folder", folder)); + } + + buildTools = new File(folder, "build-tools"); + if (!buildTools.exists()) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_build_tools_folder", folder)); + } + + platforms = new File(folder, "platforms"); + if (!platforms.exists()) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_platforms_folder", folder)); + } + + // Retrieve the highest platform from the available targets + ArrayList targets = getAvailableSdkTargets(); + int highestBuild = 1; + int highestTarget = 1; + String highestName = ""; + for (Target targ: targets) { + if (highestBuild < targ.build) { + highestBuild = targ.build; + highestTarget = targ.sdk; + highestName = targ.name; + } + } + + if (highestTarget < PApplet.parseInt(AndroidBuild.TARGET_SDK)) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_target_platform", + AndroidBuild.TARGET_SDK, platforms.getAbsolutePath())); + } + + highestPlatform = new File(platforms, highestName); + androidJar = new File(highestPlatform, "android.jar"); + if (!androidJar.exists()) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_android_jar", + AndroidBuild.TARGET_SDK, highestPlatform.getAbsolutePath())); + } + + // Collecting the tools needed by the mode + adb = findCliTool(platformTools, "adb"); + avdManager = findCliTool(new File(cmdlineTools, "bin"), "avdmanager"); + sdkManager = findCliTool(new File(cmdlineTools, "bin"), "sdkmanager"); + + initEmu(); + + String path = Platform.getenv("PATH"); + + Platform.setenv("ANDROID_SDK", folder.getCanonicalPath()); + path = platformTools.getCanonicalPath() + File.pathSeparator + + cmdlineTools.getCanonicalPath() + File.pathSeparator + path; + + String javaHomeProp = System.getProperty("java.home"); + File javaHome = new File(javaHomeProp).getCanonicalFile(); + Platform.setenv("JAVA_HOME", javaHome.getCanonicalPath()); + + path = new File(javaHome, "bin").getCanonicalPath() + File.pathSeparator + path; + Platform.setenv("PATH", path); + + checkDebugCertificate(); + } + + private void initEmu() throws BadSDKException, IOException { + File emuFolder = new File(folder, "emulator"); + if (emuFolder.exists()) { + // First try the new location of the emulator inside its own folder + emulator = findCliTool(emuFolder, "emulator"); + } else { + // If not found, use old location inside tools as fallback + emuFolder = new File(cmdlineTools, "emulator"); + if (emuFolder.exists()) { + emulator = findCliTool(cmdlineTools, "emulator"); + } else { + emulator = null; + if (SDKDownloader.DOWNLOAD_EMU_WITH_SDK) { + // Only throw an exception if the downloader was supposed to download the emulator + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.missing_emulator", + AndroidBuild.TARGET_SDK, highestPlatform.getAbsolutePath())); + } + } + } + } + public boolean downloadEmuOnDemand() { + final String[] cmd = new String[] { + sdkManager.getAbsolutePath(), + "emulator" + }; + + ProcessBuilder pb = new ProcessBuilder(cmd); + Process process = null; + try { + process = pb.start(); + } catch (IOException e) { + e.printStackTrace(); + } + + try { + new RedirectStreamHandler(new PrintWriter(System.out, true), process.getInputStream()); + new RedirectStreamHandler(new PrintWriter(System.out, true), process.getErrorStream()); + + int emulatorDownloadResultCode = process.waitFor(); + System.out.println("Output from emulator download " + emulatorDownloadResultCode); + if (emulatorDownloadResultCode == 0) { + initEmu(); + return true; + } + } catch (IOException e) { + e.printStackTrace(); + } catch (BadSDKException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + process.destroy(); + } + + return false; + } + + + /** + * If a debug certificate exists, check its expiration date. If it's expired, + * remove it so that it doesn't cause problems during the build. + */ + protected void checkDebugCertificate() { + File dotAndroidFolder = new File(System.getProperty("user.home"), ".android"); + File keystoreFile = new File(dotAndroidFolder, "debug.keystore"); + if (keystoreFile.exists()) { + // keytool -list -v -storepass android -keystore debug.keystore + ProcessHelper ph = new ProcessHelper(new String[] { + "keytool", "-list", "-v", + "-storepass", "android", + "-keystore", keystoreFile.getAbsolutePath() + }); + try { + ProcessResult result = ph.execute(); + if (result.succeeded()) { + // Valid from: Mon Nov 02 15:38:52 EST 2009 until: Tue Nov 02 16:38:52 EDT 2010 + String[] lines = PApplet.split(result.getStdout(), '\n'); + for (String line : lines) { + String[] m = PApplet.match(line, "Valid from: .* until: (.*)"); + if (m != null) { + String timestamp = m[1].trim(); + // "Sun Jan 22 11:09:08 EST 2012" + // Hilariously, this is the format of Date.toString(), however + // it isn't the default for SimpleDateFormat or others. Yay! + DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); + try { + Date date = df.parse(timestamp); + long expireMillis = date.getTime(); + if (expireMillis < System.currentTimeMillis()) { + System.out.println(AndroidMode.getTextString("android_debugger.info.removing_expired_keystore")); + String hidingName = "debug.keystore." + AndroidMode.getDateStamp(expireMillis); + File hidingFile = new File(keystoreFile.getParent(), hidingName); + if (!keystoreFile.renameTo(hidingFile)) { + System.err.println(AndroidMode.getTextString("android_debugger.error.cannot_remove_expired_keystore")); + System.err.println(AndroidMode.getTextString("android_debugger.error.request_removing_keystore", keystoreFile.getAbsolutePath())); + } + } + } catch (ParseException pe) { + System.err.println(AndroidMode.getTextString("android_debugger.error.invalid_keystore_timestamp", timestamp)); + System.err.println(AndroidMode.getTextString("android_debugger.error.request_bug_report")); + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + + public File getFolder() { + return folder; + } + + + public File getBuildToolsFolder() { + return buildTools; + } + + + public File getPlatformToolsFolder() { + return platformTools; + } + + + public File getAndroidJarPath() { + return androidJar; + } + + + public File getCommandLineToolsFolder() { + return cmdlineTools; + } + + + public File getEmulatorTool() { + return emulator; + } + + + public File getAVDManagerTool() { + return avdManager; + } + + + public File getHighestPlatform() { + return highestPlatform; + } + + + public File getTargetPlatform(String target) { + return new File(platforms, "android-" + target); + } + + + // Write to the process input, so the licenses will be accepted. In + // principle, we only need 7 'y', one for the 'yes' to the first + // 'review licenses?' question, the rest for the 6 licenses, but adding + // 10 just in case, having more does not cause any trouble. + private static final String response = "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"; + + private void acceptLicenses() { + final String[] cmd = new String[] { + sdkManager.getAbsolutePath(), + "--licenses" + }; + + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.redirectErrorStream(true); + try { + Process process = pb.start(); + final OutputStream os = process.getOutputStream(); + final InputStream is = process.getInputStream(); + // Read the process output, otherwise read() will block and wait for new + // data to read + new Thread(new Runnable() { + public void run() { + byte[] b = new byte[1024]; + try { + while (is.read(b) != -1) { } + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + }, "AndroidSDK: reading licenses").start(); + Thread.sleep(3000); + os.write(response.getBytes()); + os.flush(); + os.close(); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + + static public File getHAXMInstallerFolder() { + String sdkPrefsPath = Preferences.get("android.sdk.path"); + File sdkPath = new File(sdkPrefsPath); + return new File(sdkPath, "extras/intel/HAXM"); + } + + + static public File getGoogleDriverFolder() { + String sdkPrefsPath = Preferences.get("android.sdk.path"); + File sdkPath = new File(sdkPrefsPath); + return new File(sdkPath, "extras/google/usb_driver"); + } + + + /** + * Checks a path to see if there's a tools/android file inside, a rough check + * for the SDK installation. Also figures out the name of android/android.bat/android.exe + * so that it can be called explicitly. + */ + private static File findCliTool(final File toolDir, String toolName) + throws BadSDKException { + File toolFile; + if (Platform.isWindows()) { + toolFile = new File(toolDir, toolName + ".exe"); + if (!toolFile.exists()) { + toolFile = new File(toolDir, toolName + ".bat"); + } + } else { + toolFile = new File(toolDir, toolName); + } + + if (!toolFile.exists()) { + throw new BadSDKException("Cannot find " + toolName + " in " + toolDir); + } + + if (!Platform.isWindows()) { + try { + // Get the POSIX file permissions + Path toolPath = Paths.get(toolFile.getAbsolutePath()); + Set permissions = Files.getPosixFilePermissions(toolPath); + + boolean addedPerm = false; + if (!permissions.contains(PosixFilePermission.OWNER_EXECUTE)) { + permissions.add(PosixFilePermission.OWNER_EXECUTE); + addedPerm = true; + } + if (!permissions.contains(PosixFilePermission.GROUP_EXECUTE)) { + permissions.add(PosixFilePermission.GROUP_EXECUTE); + addedPerm = true; + } + + if (addedPerm) { + // Set the missing POSIX execute (group and owner) permissions + Files.setPosixFilePermissions(toolPath, permissions); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + return toolFile; + } + + + /** + * Check for a set android.sdk.path preference. If the pref + * is set, and refers to a legitimate Android SDK, then use that. + * + * Check for the ANDROID_SDK environment variable. If the variable is set, + * and refers to a legitimate Android SDK, then use that and save the pref. + * + * Prompt the user to select an Android SDK. If the user selects a + * legitimate Android SDK, then use that, and save the preference. + * + * @return an AndroidSDK + * @throws BadSDKException + * @throws IOException + */ + public static AndroidSDK load(boolean checkEnvSDK, Frame editor) throws IOException { + loadError = NO_ERROR; + + // Give priority to preferences: + // https://github.com/processing/processing-android/issues/372 + final String sdkPrefsPath = Preferences.get("android.sdk.path"); + if (sdkPrefsPath != null && !sdkPrefsPath.equals("")) { + try { + final AndroidSDK androidSDK = new AndroidSDK(new File(sdkPrefsPath)); + Preferences.set("android.sdk.path", sdkPrefsPath); + return androidSDK; + } catch (final BadSDKException badPref) { + Preferences.unset("android.sdk.path"); + loadError = INVALID_SDK; + } + } + + final String sdkEnvPath = Platform.getenv("ANDROID_SDK"); + if (sdkEnvPath != null && !sdkEnvPath.equals("")) { + try { + final AndroidSDK androidSDK = new AndroidSDK(new File(sdkEnvPath)); + + if (checkEnvSDK && editor != null) { + // There is a valid SDK in the environment, but let's give the user + // the option to not to use it. After this, we should go straight to + // download a new SDK. + int result = showEnvSDKDialog(editor); + if (result != JOptionPane.YES_OPTION) { + loadError = SKIP_ENV_SDK; + return null; + } + } + + // Set this value in preferences.txt, in case ANDROID_SDK + // gets knocked out later. For instance, by that pesky Eclipse, + // which nukes all env variables when launching from the IDE. + Preferences.set("android.sdk.path", sdkEnvPath); + + // If we are here, it means that there was no SDK path in the preferences + // and the user wants to use the SDK found in the environment. This + // means we just installed the mode for the first time, so we show a + // welcome message with some useful info. + AndroidUtil.showMessage(AndroidMode.getTextString("android_sdk.dialog.using_existing_sdk_title"), + AndroidMode.getTextString("android_sdk.dialog.using_existing_sdk_body", + PROCESSING_FOR_ANDROID_URL, WHATS_NEW_URL)); + + return androidSDK; + } catch (final BadSDKException badEnv) { + Preferences.unset("android.sdk.path"); + loadError = INVALID_SDK; + } + } else if (loadError == NO_ERROR) { + loadError = MISSING_SDK; + } + + return null; + } + + + static public AndroidSDK locate(final Frame window, final AndroidMode androidMode) + throws BadSDKException, CancelException, IOException { + + if (loadError == SKIP_ENV_SDK) { + // The user does not want to use the environment SDK, so let's simply + // download a new one to the sketchbook folder. + return download(window, androidMode); + } + + // At this point, there is no ANDROID_SDK env variable, no SDK in the preferences, + // or either one was invalid, so we will continue by asking the user to either locate + // a valid SDK manually, or download a new one. + int result = showLocateDialog(window); + + if (result == JOptionPane.YES_OPTION) { + return download(window, androidMode); + } else if (result == JOptionPane.NO_OPTION) { + // User will manually select folder containing SDK folder + File folder = selectFolder(AndroidMode.getTextString("android_sdk.dialog.select_sdk_folder"), null, window); + if (folder == null) { + throw new CancelException(AndroidMode.getTextString("android_sdk.error.cancel_sdk_selection")); + } else { + final AndroidSDK androidSDK = new AndroidSDK(folder); + Preferences.set("android.sdk.path", folder.getAbsolutePath()); + return androidSDK; + } + } else { + throw new CancelException(AndroidMode.getTextString("android_sdk.error.sdk_selection_canceled")); + } + } + + static public boolean requestSysImage(final Frame window, + final AndroidMode androidMode, final boolean wear, final boolean ask) + throws BadSDKException, CancelException, IOException { + final int result = showDownloadSysImageDialog(window, wear); + if (result == JOptionPane.YES_OPTION) { + return downloadSysImage(window, androidMode, wear, ask); + } else if (result == JOptionPane.NO_OPTION) { + return false; + } else { + return false; + } + } + + static public AndroidSDK download(final Frame editor, final AndroidMode androidMode) + throws BadSDKException, CancelException { + final SDKDownloader downloader = new SDKDownloader(editor); + downloader.run(); // This call blocks until the SDK download complete, or user cancels. + + if (downloader.cancelled()) { + throw new CancelException(AndroidMode.getTextString("android_sdk.error.sdk_download_canceled")); + } + AndroidSDK sdk = downloader.getSDK(); + if (sdk == null) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.sdk_download_failed")); + } + + final int result = showSDKLicenseDialog(editor); + if (result == JOptionPane.YES_OPTION) { + sdk.acceptLicenses(); + String msg = AndroidMode.getTextString("android_sdk.dialog.sdk_installed_body", PROCESSING_FOR_ANDROID_URL, WHATS_NEW_URL); + File driver = AndroidSDK.getGoogleDriverFolder(); + if (Platform.isWindows() && driver.exists()) { + msg += AndroidMode.getTextString("android_sdk.dialog.install_usb_driver", DRIVER_INSTALL_URL, driver.getAbsolutePath()); + } + AndroidUtil.showMessage(AndroidMode.getTextString("android_sdk.dialog.sdk_installed_title"), msg); + } else { + AndroidUtil.showMessage(AndroidMode.getTextString("android_sdk.dialog.sdk_license_rejected_title"), + AndroidMode.getTextString("android_sdk.dialog.sdk_license_rejected_body")); + } + + // if (Platform.isLinux() && Platform.getNativeBits() == 32) { + // AndroidUtil.showMessage(AndroidMode.getTextString("android_sdk.dialog.32bit_system_title"), + // AndroidMode.getTextString("android_sdk.dialog.32bit_system_body", SYSTEM_32BIT_URL)); + // } + + return sdk; + } + + static public boolean downloadSysImage(final Frame editor, + final AndroidMode androidMode, final boolean wear, final boolean ask) + throws BadSDKException, CancelException { + final SysImageDownloader downloader = new SysImageDownloader(editor, wear, ask); + downloader.run(); // This call blocks until the SDK download complete, or user cancels. + + if (downloader.cancelled()) { + throw new CancelException(AndroidMode.getTextString("android_sdk.error.emulator_download_canceled")); + } + boolean res = downloader.getResult(); + if (!res) { + throw new BadSDKException(AndroidMode.getTextString("android_sdk.error.emulator_download_failed")); + } + return res; + } + + + static public int showEnvSDKDialog(Frame editor) { + String title = AndroidMode.getTextString("android_sdk.dialog.found_installed_sdk_title"); + String htmlString = " " + + " " + + "

    " + AndroidMode.getTextString("android_sdk.dialog.found_installed_sdk_body") + "

    "; + JEditorPane pane = new JEditorPane("text/html", htmlString); + pane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { + Platform.openURL(e.getURL().toString()); + } + } + }); + pane.setEditable(false); + JLabel label = new JLabel(); + pane.setBackground(label.getBackground()); + + String[] options = new String[] { AndroidMode.getTextString("android_sdk.option.use_existing_sdk"), + AndroidMode.getTextString("android_sdk.option.download_new_sdk") }; + int result = JOptionPane.showOptionDialog(null, pane, title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options, options[0]); + if (result == JOptionPane.YES_OPTION) { + return JOptionPane.YES_OPTION; + } else if (result == JOptionPane.NO_OPTION) { + return JOptionPane.NO_OPTION; + } else { + return JOptionPane.CLOSED_OPTION; + } + } + + + static public int showLocateDialog(Frame editor) { + // How to show a option dialog containing clickable links: + // http://stackoverflow.com/questions/8348063/clickable-links-in-joptionpane + String htmlString = " " + + " "; + String title = ""; + if (loadError == MISSING_SDK) { + htmlString += "

    " + AndroidMode.getTextString("android_sdk.dialog.cannot_find_sdk_body", SDK_DOWNLOAD_URL, AndroidBuild.TARGET_SDK) + "

    "; + title = AndroidMode.getTextString("android_sdk.dialog.cannot_find_sdk_title"); + } else if (loadError == INVALID_SDK) { + htmlString += "

    " + AndroidMode.getTextString("android_sdk.dialog.invalid_sdk_body", AndroidBuild.TARGET_SDK, SDK_DOWNLOAD_URL, AndroidBuild.TARGET_SDK) + "

    "; + title = AndroidMode.getTextString("android_sdk.dialog.invalid_sdk_title"); + } + JEditorPane pane = new JEditorPane("text/html", htmlString); + pane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { + Platform.openURL(e.getURL().toString()); + } + } + }); + pane.setEditable(false); + JLabel label = new JLabel(); + pane.setBackground(label.getBackground()); + + String[] options = new String[] { AndroidMode.getTextString("android_sdk.option.download_sdk"), + AndroidMode.getTextString("android_sdk.option.locate_sdk") }; + int result = JOptionPane.showOptionDialog(null, pane, title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options, options[0]); + if (result == JOptionPane.YES_OPTION) { + return JOptionPane.YES_OPTION; + } else if (result == JOptionPane.NO_OPTION) { + return JOptionPane.NO_OPTION; + } else { + return JOptionPane.CLOSED_OPTION; + } + } + + + static public int showDownloadSysImageDialog(Frame editor, boolean wear) { + String title = wear ? AndroidMode.getTextString("android_sdk.dialog.download_watch_image_title") : + AndroidMode.getTextString("android_sdk.dialog.download_phone_image_title"); + String msg = wear ? AndroidMode.getTextString("android_sdk.dialog.download_watch_image_body") : + AndroidMode.getTextString("android_sdk.dialog.download_phone_image_body"); + String htmlString = " " + + " " + "

    " + msg + "

    "; + JEditorPane pane = new JEditorPane("text/html", htmlString); + pane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { + Platform.openURL(e.getURL().toString()); + } + } + }); + pane.setEditable(false); + JLabel label = new JLabel(); + pane.setBackground(label.getBackground()); + + String[] options = new String[] { Language.text("prompt.yes"), Language.text("prompt.no") }; + + int result = JOptionPane.showOptionDialog(null, pane, title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options, options[0]); + if (result == JOptionPane.YES_OPTION) { + return JOptionPane.YES_OPTION; + } else if (result == JOptionPane.NO_OPTION) { + return JOptionPane.NO_OPTION; + } else { + return JOptionPane.CLOSED_OPTION; + } + } + + + static public int showSDKLicenseDialog(Frame editor) { + String title = AndroidMode.getTextString("android_sdk.dialog.accept_sdk_license_title"); + String msg = AndroidMode.getTextString("android_sdk.dialog.accept_sdk_license_body", SDK_LICENSE_URL); + String htmlString = " " + + " " + "

    " + msg + "

    "; + JEditorPane pane = new JEditorPane("text/html", htmlString); + pane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { + Platform.openURL(e.getURL().toString()); + } + } + }); + pane.setEditable(false); + JLabel label = new JLabel(); + pane.setBackground(label.getBackground()); + + String[] options = new String[] { Language.text("prompt.yes"), Language.text("prompt.no") }; + + int result = JOptionPane.showOptionDialog(null, pane, title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options, options[0]); + if (result == JOptionPane.YES_OPTION) { + return JOptionPane.YES_OPTION; + } else if (result == JOptionPane.NO_OPTION) { + return JOptionPane.NO_OPTION; + } else { + return JOptionPane.CLOSED_OPTION; + } + } + + + // this was banished from Base because it encourages bad practice. + // TODO figure out a better way to handle the above. + static public File selectFolder(String prompt, File folder, Frame frame) { + if (Platform.isMacOS()) { + if (frame == null) frame = new Frame(); //.pack(); + FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD); + if (folder != null) { + fd.setDirectory(folder.getParent()); + //fd.setFile(folder.getName()); + } + System.setProperty("apple.awt.fileDialogForDirectories", "true"); + fd.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); + fd.setVisible(true); + System.setProperty("apple.awt.fileDialogForDirectories", "false"); + if (fd.getFile() == null) { + return null; + } + return new File(fd.getDirectory(), fd.getFile()); + + } else { + JFileChooser fc = new JFileChooser(); + fc.setDialogTitle(prompt); + if (folder != null) { + fc.setSelectedFile(folder); + } + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + + int returned = fc.showOpenDialog(frame); + if (returned == JFileChooser.APPROVE_OPTION) { + return fc.getSelectedFile(); + } + } + return null; + } + + + private static final String ADB_DAEMON_MSG_1 = "daemon not running"; + private static final String ADB_DAEMON_MSG_2 = "daemon started successfully"; + + public ProcessResult runAdb(final String... cmd) + throws InterruptedException, IOException { + + if (adbDisabled) { + throw new IOException("adb is currently disabled"); + } + + final String[] adbCmd = PApplet.splice(cmd, adb.getCanonicalPath(), 0); + + if (processing.app.Base.DEBUG) { + // printing this here to see if anyone else is killing the adb server + PApplet.printArray(adbCmd); + } + + try { + ProcessResult adbResult = new ProcessHelper(adbCmd).execute(); + // Ignore messages about starting up an adb daemon + String out = adbResult.getStdout(); + if (out.contains(ADB_DAEMON_MSG_1) && out.contains(ADB_DAEMON_MSG_2)) { + StringBuilder sb = new StringBuilder(); + for (String line : out.split("\n")) { + if (!out.contains(ADB_DAEMON_MSG_1) && + !out.contains(ADB_DAEMON_MSG_2)) { + sb.append(line).append("\n"); + } + } + return new ProcessResult(adbResult.getCmd(), + adbResult.getResult(), + sb.toString(), + adbResult.getStderr(), + adbResult.getTime()); + } + return adbResult; + } catch (IOException ioe) { + if (-1 < ioe.getMessage().indexOf("Permission denied")) { + Messages.showWarning(AndroidMode.getTextString("android_sdk.warn.cannot_run_adb_title"), + AndroidMode.getTextString("android_sdk.warn.cannot_run_adb_body")); + adbDisabled = true; + } + throw ioe; + } + } + + public Process getAdbProcess(final String... cmd) + throws IOException { + + if (adbDisabled) { + throw new IOException("adb is currently disabled"); + } + + final String[] adbCmd = PApplet.splice(cmd, adb.getCanonicalPath(), 0); + + if (processing.app.Base.DEBUG) { + // printing this here to see if anyone else is killing the adb server + PApplet.printArray(adbCmd); + } + + try { + Process process = Runtime.getRuntime().exec(adbCmd); + return process; + } catch (IOException ioe) { + if (-1 < ioe.getMessage().indexOf("Permission denied")) { + Messages.showWarning(AndroidMode.getTextString("android_sdk.warn.cannot_run_adb_title"), + AndroidMode.getTextString("android_sdk.warn.cannot_run_adb_body")); + adbDisabled = true; + } + throw ioe; + } + } + + static private class Target { + public int sdk = 0; + public String release = ""; + public int build = 0; + public String name = ""; + } + + private ArrayList getAvailableSdkTargets() throws IOException { + ArrayList targets = new ArrayList(); + + for (File platform : platforms.listFiles()) { + File propFile = new File(platform, "build.prop"); + if (!propFile.exists()) continue; + + Target target = new Target(); + + BufferedReader br = new BufferedReader(new FileReader(propFile)); + String line; + while ((line = br.readLine()) != null) { + String[] lineData = line.split("="); + + if (lineData[0].equals("ro.system.build.version.incremental")) { + target.build = Integer.valueOf(lineData[1]); + } + + if (lineData[0].equals("ro.build.version.release")) { + target.release = lineData[1]; + } + + if (lineData[0].equals("ro.build.version.sdk")) { + target.sdk = Integer.valueOf(lineData[1]); + } + + target.name = platform.getName(); + } + br.close(); + + if (target.sdk != 0 && target.build != 0 && target.name != "") targets.add(target); + } + + return targets; + } + + @SuppressWarnings("serial") + static public class BadSDKException extends Exception { + public BadSDKException(final String message) { + super(message); + } + } + + @SuppressWarnings("serial") + static public class CancelException extends Exception { + public CancelException(final String message) { + super(message); + } + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidTool.java b/processing/mode/src/processing/mode/android/AndroidTool.java new file mode 100644 index 000000000..7bab241d7 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidTool.java @@ -0,0 +1,145 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2017-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.mode.android; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; + +import processing.app.Base; +import processing.app.Messages; +import processing.app.Util; +import processing.app.contrib.ContributionType; +import processing.app.contrib.IgnorableException; +import processing.app.contrib.LocalContribution; +import processing.app.tools.Tool; + +/** + * Specialized local contribution for Android tools. Cannot use ToolContribution + * from processing-app since Android tools may need SDK jars in the classpath. + */ +public class AndroidTool extends LocalContribution implements Tool, Comparable { + private Tool tool; + private AndroidMode mode; + + AndroidTool(File toolFolder, AndroidMode androidMode) throws Throwable { + super(toolFolder); + this.mode = androidMode; + + String className = initLoader(null); + if (className != null) { + Class toolClass = loader.loadClass(className); + tool = (Tool) toolClass.newInstance(); + } + } + + public void init(Base base) { + tool.init(base); + } + + + public void run() { + tool.run(); + } + + + public String getMenuTitle() { + return tool.getMenuTitle(); + } + + public String initLoader(String className) throws Exception { + File toolDir = new File(folder, "tool"); + if (toolDir.exists()) { + Messages.log("checking mode folder regarding " + className); + // If no class name specified, search the main .jar for the + // full name package and mode name. + if (className == null) { + String shortName = folder.getName(); + File mainJar = new File(toolDir, shortName + ".jar"); + if (mainJar.exists()) { + className = findClassInZipFile(shortName, mainJar); + } else { + throw new IgnorableException(mainJar.getAbsolutePath() + " does not exist."); + } + + if (className == null) { + throw new IgnorableException("Could not find " + shortName + + " class inside " + mainJar.getAbsolutePath()); + } + } + + // Add .jar and .zip files from the "tool" and the tools/lib + // folder into the classpath + File libDir = new File(folder, "lib"); + File[] toolArchives = Util.listJarFiles(toolDir); + File[] libArchives = Util.listJarFiles(libDir); + + if (toolArchives != null && toolArchives.length > 0) { + + int nArchives = toolArchives.length + 1; + if (libArchives != null && libArchives.length > 0) { + nArchives += libArchives.length; + } + URL[] urlList = new URL[nArchives]; + + int j; + for (j = 0; j < toolArchives.length; j++) { + Messages.log("Found archive " + toolArchives[j] + " for " + getName()); + urlList[j] = toolArchives[j].toURI().toURL(); + } + if (libArchives != null) { + for (int k = 0; k < libArchives.length; k++, j++) { + Messages.log("Found archive " + libArchives[k] + " for " + getName()); + urlList[j] = libArchives[k].toURI().toURL(); + } + } + urlList[urlList.length - 1] = new File(mode.getModeJar()).toURI().toURL(); + +// String modePath = new File(dmode.getFolder(), "mode").getAbsolutePath(); +// urlList[urlList.length - 1] = new File(modePath + File.separator + "JavaMode.jar").toURI().toURL(); + + loader = new URLClassLoader(urlList); + Messages.log("loading above JARs with loader " + loader); + } else { + throw new IgnorableException("Could not find any files inside " + toolDir.getAbsolutePath()); + } + } + + // If no archives were found, just use the regular ClassLoader + if (loader == null) { + loader = Thread.currentThread().getContextClassLoader(); + } + return className; + } + + + @Override + public int compareTo(AndroidTool o) { + return getMenuTitle().compareTo(o.getMenuTitle()); + } + + + @Override + public ContributionType getType() { + return ContributionType.TOOL; + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidToolbar.java b/processing/mode/src/processing/mode/android/AndroidToolbar.java new file mode 100644 index 000000000..960cd30b0 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidToolbar.java @@ -0,0 +1,218 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2011-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + + +package processing.mode.android; + +import java.awt.event.ActionEvent; +import java.awt.event.InputEvent; +import java.util.ArrayList; +import java.util.List; + +import processing.app.Base; +import processing.app.ui.Editor; +import processing.app.ui.EditorButton; +import processing.app.ui.EditorToolbar; +import processing.app.Language; + +import javax.swing.*; + + +@SuppressWarnings("serial") +public class AndroidToolbar extends EditorToolbar { + static protected final int RUN_ON_DEVICE = 0; + static protected final int RUN_IN_EMULATOR = 1; + static protected final int STOP = 2; + + static protected final int NEW = 3; + static protected final int OPEN = 4; + static protected final int SAVE = 5; + + static protected final int EXPORT_PACKAGE = 6; + static protected final int EXPORT_BUNDLE = 7; + static protected final int EXPORT_PROJECT = 8; + + + private AndroidEditor aEditor; + + EditorButton stepButton; + EditorButton continueButton; + + public AndroidToolbar(Editor editor, Base base) { + super(editor); + aEditor = (AndroidEditor) editor; + } + + + static public String getTitle(int index) { + switch (index) { + case RUN_ON_DEVICE: return AndroidMode.getTextString("menu.sketch.run_on_device"); + case RUN_IN_EMULATOR: return AndroidMode.getTextString("menu.sketch.run_in_emulator"); + case STOP: return AndroidMode.getTextString("menu.sketch.stop"); + case NEW: return AndroidMode.getTextString("menu.file.new"); + case OPEN: return AndroidMode.getTextString("menu.file.open"); + case SAVE: return AndroidMode.getTextString("menu.file.save"); + case EXPORT_PACKAGE: return AndroidMode.getTextString("menu.file.export_signed_package"); + case EXPORT_BUNDLE: return AndroidMode.getTextString("menu.file.export_signed_bundle"); + case EXPORT_PROJECT: return AndroidMode.getTextString("menu.file.export_android_project"); + } + return null; + } + + + @Override + public List createButtons() { + // aEditor not ready yet because this is called by super() + final boolean debug = ((AndroidEditor) editor).isDebuggerEnabled(); + // final boolean debug = false; + + + ArrayList toReturn = new ArrayList(); + final String runText = debug ? + Language.text("toolbar.debug") : Language.text("Run on Device"); + runButton = new EditorButton(this, + "/lib/toolbar/run", + runText, + "Run on emulator") { + @Override + public void actionPerformed(ActionEvent e) { + handleRun(e.getModifiers()); + } + }; + toReturn.add(runButton); + + if (debug) { + stepButton = new EditorButton(this, + "/lib/toolbar/step", + Language.text("menu.debug.step"), + Language.text("menu.debug.step_into"), + Language.text("menu.debug.step_out")) { + @Override + public void actionPerformed(ActionEvent e) { + final int mask = ActionEvent.SHIFT_MASK | ActionEvent.ALT_MASK; + handleStep(e.getModifiers() & mask); + } + }; + toReturn.add(stepButton); + + continueButton = new EditorButton(this, + "/lib/toolbar/continue", + Language.text("menu.debug.continue")) { + @Override + public void actionPerformed(ActionEvent e) { + aEditor.getDebugger().continueDebug(); + } + }; + toReturn.add(continueButton); + } + + stopButton = new EditorButton(this, + "/lib/toolbar/stop", + Language.text("toolbar.stop")) { + @Override + public void actionPerformed(ActionEvent e) { + handleStop(); + } + }; + toReturn.add(stopButton); + + return toReturn; + } + + private void handleStep(int modifiers) { + if (modifiers == 0) { + aEditor.getDebugger().stepOver(); + } else if ((modifiers & ActionEvent.SHIFT_MASK) != 0) { + aEditor.getDebugger().stepInto(); + } else if ((modifiers & ActionEvent.ALT_MASK) != 0) { + aEditor.getDebugger().stepOut(); + } + } + + @Override + public void addModeButtons(Box box, JLabel label) { + + EditorButton debugButton = + new EditorButton(this, "/lib/toolbar/debug", + Language.text("toolbar.debug")) { + @Override + public void actionPerformed(ActionEvent e) { + aEditor.toggleDebug(); + } + }; + + if (((AndroidEditor) editor).isDebuggerEnabled()) { + debugButton.setSelected(true); + } +// debugButton.setRolloverLabel(label); + box.add(debugButton); + addGap(box); + + } + + @Override + public void handleRun(int modifiers) { + boolean shift = (modifiers & InputEvent.SHIFT_MASK) != 0; + if (!shift) { + aEditor.handleRunDevice(); + } else { + aEditor.handleRunEmulator(); + } + } + + + @Override + public void handleStop() { + // TODO Auto-generated method stub + aEditor.handleStop(); + } + + + public void activateExport() { + // TODO added to match the new API in EditorToolbar (activateRun, etc). + } + + + public void deactivateExport() { + // TODO added to match the new API in EditorToolbar (activateRun, etc). + } + + public void activateContinue() { + continueButton.setSelected(true); + repaint(); + } + + public void deactivateContinue() { + continueButton.setSelected(false); + repaint(); + } + + public void activateStep() { + stepButton.setSelected(true); + repaint(); + } + + public void deactivateStep() { + stepButton.setSelected(false); + repaint(); + } +} diff --git a/processing/mode/src/processing/mode/android/AndroidUtil.java b/processing/mode/src/processing/mode/android/AndroidUtil.java new file mode 100644 index 000000000..9e0a215f4 --- /dev/null +++ b/processing/mode/src/processing/mode/android/AndroidUtil.java @@ -0,0 +1,325 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2017-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.nio.file.Paths; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.FileVisitResult; +import java.nio.file.attribute.BasicFileAttributes; + +import javax.swing.JEditorPane; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; + +import processing.app.Base; +import processing.app.Messages; +import processing.app.Mode; +import processing.app.Platform; +import processing.app.SketchException; +import processing.app.Util; +import processing.app.exec.ProcessHelper; +import processing.app.exec.ProcessResult; +import processing.app.ui.Toolkit; +import processing.core.PApplet; + +/** + * Some utilities. + */ +public class AndroidUtil { + final static private int FONT_SIZE = Toolkit.zoom(11); + final static private int TEXT_MARGIN = Toolkit.zoom(8); + final static private int TEXT_WIDTH = Toolkit.zoom(300); + + // Creates a message dialog, where the text can contain clickable links. + static public void showMessage(String title, String text) { + System.out.println(text); + if (title == null) title = "Message"; + if (Base.isCommandLine()) { + System.out.println(title + ": " + text); + } else { + String htmlString = " " + + " " + + "

    " + text + "

    "; + JEditorPane pane = new JEditorPane(); + pane.setContentType("text/html"); + pane.setText(htmlString); + pane.setEditable(false); + + pane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { + if (e.getURL() != null) { + Platform.openURL(e.getURL().toString()); + } else { + String description = e.getDescription(); + System.err.println("Cannot open this URL: " + description); + } + } + } + }); + + JLabel label = new JLabel(); + pane.setBackground(label.getBackground()); + JOptionPane.showMessageDialog(null, pane, title, + JOptionPane.INFORMATION_MESSAGE); + } + } + + static public void writeFile(final File file, String[] lines) { + final PrintWriter writer = PApplet.createWriter(file); + for (String line: lines) writer.println(line); + writer.flush(); + writer.close(); + } + + static public File createPath(final File parent, final String name) + throws SketchException { + final File result = new File(parent, name); + if (!(result.exists() || result.mkdirs())) { + throw new SketchException("Could not create " + result); + } + return result; + } + + static public void createFileFromTemplate(final File tmplFile, final File destFile) { + createFileFromTemplate(tmplFile, destFile, null); + } + + static public void createFileFromTemplate(final File tmplFile, final File destFile, + final HashMap replaceMap) { + PrintWriter pw = PApplet.createWriter(destFile); + String lines[] = PApplet.loadStrings(tmplFile); + for (int i = 0; i < lines.length; i++) { + if (lines[i].indexOf("@@") != -1 && replaceMap != null) { + StringBuilder sb = new StringBuilder(lines[i]); + int index = 0; + for (String key: replaceMap.keySet()) { + String val = replaceMap.get(key); + while ((index = sb.indexOf(key)) != -1) { + sb.replace(index, index + key.length(), val); + } + } + lines[i] = sb.toString(); + } + // explicit newlines to avoid Windows CRLF + pw.print(lines[i] + "\n"); + } + pw.flush(); + pw.close(); + } + + static public File createSubFolder(File parent, String name) throws IOException { + File newFolder = new File(parent, name); + if (newFolder.exists()) { + String stamp = AndroidMode.getDateStamp(newFolder.lastModified()); + File dest = new File(parent, name + "." + stamp); + boolean result = newFolder.renameTo(dest); + if (!result) { + ProcessHelper mv; + ProcessResult pr; + try { + System.err.println("Cannot rename existing " + name + " folder, resorting to mv/move instead."); + mv = new ProcessHelper("mv", newFolder.getAbsolutePath(), dest.getAbsolutePath()); + pr = mv.execute(); + } catch (InterruptedException e) { + e.printStackTrace(); + return null; + } + if (!pr.succeeded()) { + System.err.println(pr.getStderr()); + Messages.showWarning("Failed to rename", + "Could not rename the old \"" + name + "\" folder.\n" + + "Please delete, close, or rename the folder\n" + + newFolder.getAbsolutePath() + "\n" + + "and try again." , null); + Platform.openFolder(newFolder); + return null; + } + } + } else { + boolean result = newFolder.mkdirs(); + if (!result) { + Messages.showWarning("Folders, folders, folders", + "Could not create the necessary folders to build.\n" + + "Perhaps you have some file permissions to sort out?", null); + return null; + } + } + return newFolder; + } + + static public void extractFolder(File file, File newPath) + throws IOException { + int BUFFER = 2048; + + ZipFile zip = new ZipFile(file); + Enumeration zipFileEntries = zip.entries(); + + // Process each entry + while (zipFileEntries.hasMoreElements()) { + // grab a zip file entry + ZipEntry entry = zipFileEntries.nextElement(); + String currentEntry = entry.getName(); + + File destFile = new File(newPath, currentEntry); + //destFile = new File(newPath, destFile.getName()); + File destinationParent = destFile.getParentFile(); + + // create the parent directory structure if needed + destinationParent.mkdirs(); + + if (!entry.isDirectory()) { + // should preserve permissions + // https://bitbucket.org/atlassian/amps/pull-requests/21/amps-904-preserve-executable-file-status/diff + BufferedInputStream is = new BufferedInputStream(zip.getInputStream(entry)); + int currentByte; + // establish buffer for writing file + byte data[] = new byte[BUFFER]; + + // write the current file to disk + FileOutputStream fos = new FileOutputStream(destFile); + BufferedOutputStream dest = new BufferedOutputStream(fos, BUFFER); + + // read and write until last byte is encountered + while ((currentByte = is.read(data, 0, BUFFER)) != -1) { + dest.write(data, 0, currentByte); + } + dest.flush(); + dest.close(); + is.close(); + } + } + zip.close(); + } + + static public void extractClassesJarFromAar(File wearFile, File explodeDir, + File jarFile) throws IOException { + extractClassesJarFromAar(wearFile, explodeDir, jarFile, true); + } + + static public void extractClassesJarFromAar(File wearFile, File explodeDir, + File jarFile, boolean removeDir) throws IOException { + extractFolder(wearFile, explodeDir); + File classFile = new File(explodeDir, "classes.jar"); + Util.copyFile(classFile, jarFile); + Util.removeDir(explodeDir); + } + + static public File[] getFileList(File folder, String[] names) { + return getFileList(folder, names, null); + } + + static public File[] getFileList(File folder, String[] names, String[] altNames) { + File[] icons = new File[names.length]; + for (int i = 0; i < names.length; i++) { + File f = new File(folder, names[i]); + if (!f.exists() && altNames != null) { + f = new File(folder, altNames[i]); + } + icons[i] = f; + } + return icons; + } + + static public File[] getFileList(Mode mode, String prefix, String[] names) { + File[] icons = new File[names.length]; + for (int i = 0; i < names.length; i++) { + icons[i] = mode.getContentFile(prefix + names[i]); + } + return icons; + } + + static public boolean allFilesExists(File[] files) { + for (File f: files) { + if (!f.exists()) return false; + } + return true; + } + + static public boolean noFileExists(File[] files) { + for (File f: files) { + if (f.exists()) return false; + } + return true; + } + + static public void moveDir(File from, File to) { + try { + Files.move(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + ex.printStackTrace(); + } + } + + static public void copyDir(File from, File to) { + final Path source = Paths.get(from.toURI()); + final Path target = Paths.get(to.toURI()); + + SimpleFileVisitor copyVisitor = new SimpleFileVisitor() { + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + Path resolve = target.resolve(source.relativize(dir)); + if (Files.notExists(resolve)) Files.createDirectories(resolve); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Path resolve = target.resolve(source.relativize(file)); + Files.copy(file, resolve, StandardCopyOption.REPLACE_EXISTING); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult visitFileFailed(Path file, IOException exc) { + System.err.format("Unable to copy: %s: %s%n", file, exc); + return FileVisitResult.CONTINUE; + } + }; + + try { + Files.walkFileTree(source, copyVisitor); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/processing/mode/android/Commander.java b/processing/mode/src/processing/mode/android/Commander.java similarity index 82% rename from src/processing/mode/android/Commander.java rename to processing/mode/src/processing/mode/android/Commander.java index c29c35c16..437dab96b 100644 --- a/src/processing/mode/android/Commander.java +++ b/processing/mode/src/processing/mode/android/Commander.java @@ -3,12 +3,12 @@ /* Part of the Processing project - http://processing.org + Copyright (c) 2012-21 The Processing Foundation Copyright (c) 2008-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -29,10 +29,12 @@ import java.util.Arrays; import processing.app.Base; +import processing.app.Platform; import processing.app.Preferences; import processing.app.RunnerListener; import processing.app.Sketch; import processing.app.SketchException; +import processing.app.Util; import processing.app.contrib.ModeContribution; /** @@ -48,7 +50,13 @@ public class Commander implements RunnerListener { static final String runArg_EMULATOR = "e"; static final String targetArg = "--target"; static final String targetArg_DEBUG = "debug"; - static final String targetArg_RELEASE = "release"; + static final String targetArg_RELEASE = "release"; + static final String componentArg = "--component"; + static final String targetArg_FRAGMENT = "fragment"; + static final String targetArg_WALLPAPER = "wallpaper"; + static final String targetArg_WATCHFACE = "watchface"; + static final String targetArg_VR = "vr"; + static final String targetArg_AR = "ar"; static final String sketchArg = "--sketch="; static final String forceArg = "--force"; static final String outputArg = "--output="; @@ -73,6 +81,8 @@ public class Commander implements RunnerListener { private String outputPath = null; private File outputFolder = null; + + private int appComponent = AndroidBuild.APP; private boolean force = false; // replace that no good output folder private String device = runArg_DEVICE; @@ -82,7 +92,7 @@ static public void main(String[] args) { // Do this early so that error messages go to the console Base.setCommandLine(); // init the platform so that prefs and other native code is ready to go - Base.initPlatform(); + Platform.init(); // launch command line handler Commander commander = new Commander(args); @@ -116,6 +126,19 @@ private void parseArgs(String[] args) { // mode already set to HELP } else if (arg.startsWith(targetArg)) { target = extractValue(arg, targetArg, targetArg_DEBUG); + } else if (arg.startsWith(componentArg)) { + String compStr = extractValue(arg, targetArg, targetArg_FRAGMENT); + if (compStr.equals(targetArg_FRAGMENT)) { + appComponent = AndroidBuild.APP; + } else if (compStr.equals(targetArg_WALLPAPER)) { + appComponent = AndroidBuild.WALLPAPER; + } else if (compStr.equals(targetArg_WATCHFACE)) { + appComponent = AndroidBuild.WATCHFACE; + } else if (compStr.equals(targetArg_VR)) { + appComponent = AndroidBuild.VR; + } else if (compStr.equals(targetArg_AR)) { + appComponent = AndroidBuild.AR; + } } else if (arg.equals(buildArg)) { task = BUILD; } else if (arg.startsWith(runArg)) { @@ -168,7 +191,7 @@ private void initValues() { outputFolder = new File(outputPath); if (outputFolder.exists()) { if (force) { - Base.removeDir(outputFolder); + Util.removeDir(outputFolder); } else { complainAndQuit("The output folder already exists. " + "Use --force to remove it.", false); } @@ -180,7 +203,7 @@ private void initValues() { checkOrQuit(sketchPath != null, "No sketch path specified.", true); checkOrQuit(!outputPath.equals(sketchPath), "The sketch path and output path cannot be identical.", false); - androidMode = (AndroidMode) ModeContribution.load(null, Base.getContentFile("modes/android"), + androidMode = (AndroidMode) ModeContribution.load(null, Platform.getContentFile("modes/android"), "processing.mode.android.AndroidMode").getMode(); androidMode.checkSDK(null); } @@ -188,14 +211,15 @@ private void initValues() { private void execute() { if (processing.app.Base.DEBUG) { systemOut.println("Build status: "); - systemOut.println("Sketch: " + sketchPath); - systemOut.println("Output: " + outputPath); - systemOut.println("Force: " + force); - systemOut.println("Target: " + target); + systemOut.println("Sketch: " + sketchPath); + systemOut.println("Output: " + outputPath); + systemOut.println("Force: " + force); + systemOut.println("Target: " + target); + systemOut.println("Component: " + appComponent); systemOut.println("==== Task ===="); - systemOut.println("--build: " + (task == BUILD)); - systemOut.println("--run: " + (task == RUN)); - systemOut.println("--export: " + (task == EXPORT)); + systemOut.println("--build: " + (task == BUILD)); + systemOut.println("--run: " + (task == RUN)); + systemOut.println("--export: " + (task == EXPORT)); systemOut.println(); } @@ -207,24 +231,25 @@ private void execute() { checkOrQuit(outputFolder.mkdirs(), "Could not create the output folder.", false); boolean success = false; - + try { + boolean runOnEmu = runArg_EMULATOR.equals(device); sketch = new Sketch(pdePath, androidMode); if (task == BUILD || task == RUN) { - AndroidBuild build = new AndroidBuild(sketch, androidMode); - build.build(target); + AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent); + build.build(target, ""); if (task == RUN) { AndroidRunner runner = new AndroidRunner(build, this); - runner.launch(runArg_EMULATOR.equals(device) ? - Devices.getInstance().getEmulator() : - Devices.getInstance().getHardware()); + runner.launch(runOnEmu ? + Devices.getInstance().getEmulator(build.isWear()) : + Devices.getInstance().getHardware(), build.getAppComponent(), runOnEmu); } success = true; } else if (task == EXPORT) { - AndroidBuild build = new AndroidBuild(sketch, androidMode); + AndroidBuild build = new AndroidBuild(sketch, androidMode, appComponent); build.exportProject(); success = true; diff --git a/src/processing/mode/android/Device.java b/processing/mode/src/processing/mode/android/Device.java similarity index 56% rename from src/processing/mode/android/Device.java rename to processing/mode/src/processing/mode/android/Device.java index 0bb456693..017250da0 100644 --- a/src/processing/mode/android/Device.java +++ b/processing/mode/src/processing/mode/android/Device.java @@ -1,3 +1,24 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2013-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + package processing.mode.android; import processing.app.Base; @@ -18,41 +39,59 @@ class Device { private final Devices env; private final String id; + private final String features; private final Set activeProcesses = new HashSet(); - private final Set listeners = + private final Set listeners = Collections.synchronizedSet(new HashSet()); - + // public static final String APP_STARTED = "android.device.app.started"; // public static final String APP_ENDED = "android.device.app.ended"; + private String packageName = ""; + // mutable state private Process logcat; public Device(final Devices env, final String id) { this.env = env; this.id = id; + + // http://android.stackexchange.com/questions/82169/howto-get-devices-features-with-adb + String concat = ""; + try { + final ProcessResult res = adb("shell", "getprop", "ro.build.characteristics"); + for (String line : res) { + concat += "," + line.toLowerCase(); + } + } catch (final Exception e) { + } + this.features = concat; } public void bringLauncherToFront() { try { - adb("shell", "am", "start", - "-a", "android.intent.action.MAIN", - "-c", "android.intent.category.HOME"); + adb("shell", "am", "start", + "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.HOME"); } catch (final Exception e) { e.printStackTrace(System.err); } } + public boolean hasFeature(String feature) { + return -1 < features.indexOf(feature); + } + public String getName() { String name = ""; try { - ProcessResult result = AndroidSDK.runADB("-s", id, "shell", "getprop", "ro.product.brand"); + ProcessResult result = adb("shell", "getprop", "ro.product.brand"); if (result.succeeded()) { name += result.getStdout() + " "; } - result = AndroidSDK.runADB("-s", id, "shell", "getprop", "ro.product.model"); + result = adb("shell", "getprop", "ro.product.model"); if (result.succeeded()) { name += result.getStdout(); } @@ -62,7 +101,13 @@ public String getName() { e.printStackTrace(); } - return name + " [" + id + "]"; + name += " [" + id + "]"; + +// if (hasFeature("watch")) { +// name += " (watch)"; +// } + + return name; } // adb -s emulator-5556 install helloWorld.apk @@ -83,8 +128,16 @@ public boolean installApp(final AndroidBuild build, final RunnerListener status) return false; } bringLauncherToFront(); + + String apkPath = build.getPathForAPK(); + if (apkPath == null) { + status.statusError("Could not install the sketch."); + System.err.println("The APK file is missing"); + return false; + } + try { - final ProcessResult installResult = adb("install", "-r", build.getPathForAPK()); + final ProcessResult installResult = adb("install", "-r", apkPath); if (!installResult.succeeded()) { status.statusError("Could not install the sketch."); System.err.println(installResult); @@ -125,23 +178,35 @@ public boolean removeApp(String packageName) throws IOException, InterruptedExce return true; } - + // different version that actually runs through JDI: // http://asantoso.wordpress.com/2009/09/26/using-jdb-with-adb-to-debugging-of-android-app-on-a-real-device/ - public boolean launchApp(final String packageName, final String className) + public boolean launchApp(final String packageName, boolean isDebuggerEnabled) throws IOException, InterruptedException { if (!isAlive()) { return false; } - String[] cmd = { - "shell", "am", "start", - "-e", "debug", "true", - "-a", "android.intent.action.MAIN", - "-c", "android.intent.category.LAUNCHER", - "-n", packageName + "/." + className - }; -// PApplet.println(cmd); - ProcessResult pr = adb(cmd); + ProcessResult pr; + if (isDebuggerEnabled) { + String[] cmd = { + "shell", "am", "start", + "-e", "debug", "true", + "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.LAUNCHER", "-D", + "-n", packageName + "/.MainActivity" + }; + pr = adb(cmd); + }else { + String[] cmd = { + "shell", "am", "start", + "-e", "debug", "true", + "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.LAUNCHER", + "-n", packageName + "/.MainActivity" + }; + pr = adb(cmd); + } + if (Base.DEBUG) { System.out.println(pr.toString()); } @@ -155,10 +220,43 @@ public boolean launchApp(final String packageName, final String className) return pr.succeeded(); } + public void forwardPort(int tcpPort) throws IOException, InterruptedException { + // Start ADB Server + adb("start-server"); + + Process deviceId = adbProc("jdwp"); + + // Get Process ID from ADB command `adb jdwp` + JDWPProcessor pIDProcessor = new JDWPProcessor(); + new StreamPump(deviceId.getInputStream(), "jdwp: ").addTarget( + pIDProcessor).start(); + new StreamPump(deviceId.getErrorStream(), "jdwperr: ").addTarget( + System.err).start(); + + Thread.sleep(1000); + + // Forward to tcp port + adb("forward", "tcp:" + tcpPort, "jdwp:" + pIDProcessor.getId()); + } + + private class JDWPProcessor implements LineProcessor { + private int pId; + public void processLine(final String line) { + pId = Integer.parseInt(line); + } + public int getId() { + return pId; + } + } + public boolean isEmulator() { return id.startsWith("emulator"); } + public void setPackageName(String pkgName) { + packageName = pkgName; + } + // I/Process ( 9213): Sending signal. PID: 9213 SIG: 9 private static final Pattern SIG = Pattern .compile("PID:\\s+(\\d+)\\s+SIG:\\s+(\\d+)"); @@ -168,12 +266,67 @@ public boolean isEmulator() { private class LogLineProcessor implements LineProcessor { public void processLine(final String line) { final LogEntry entry = new LogEntry(line); +// System.out.println("***************************************************"); +// System.out.println(line); +// System.out.println(activeProcesses); +// System.out.println(entry.message); + if (entry.message.startsWith("PROCESSING")) { + // Old start/stop process detection, does not seem to work anymore. + // Should be ok to remove at some point. if (entry.message.contains("onStart")) { startProc(entry.source, entry.pid); } else if (entry.message.contains("onStop")) { endProc(entry.pid); } + } else if (packageName != null && !packageName.equals("") && + entry.message.contains("Start proc") && + entry.message.contains(packageName)) { + // Sample message string from logcat when starting process: + // "Start proc 29318:processing.test.sketch001/u0a403 for activity processing.test.sketch001/.MainActivity" + boolean pidFound = false; + + try { + int idx0 = entry.message.indexOf("Start proc") + 11; + int idx1 = entry.message.indexOf(packageName) - 1; + String pidStr = entry.message.substring(idx0, idx1); + int pid = Integer.parseInt(pidStr); + startProc(entry.source, pid); + pidFound = true; + } catch (Exception ex) { } + + if (!pidFound) { + // In some cases (old adb maybe?): + // https://github.com/processing/processing-android/issues/331 + // the process start line is slightly different: + // I/ActivityManager( 648): Start proc processing.test.sketch_170818a for activity processing.test.sketch_170818a/.MainActivity: pid=4256 uid=10175 gids={50175} + try { + int idx0 = entry.message.indexOf("pid=") + 4; + int idx1 = entry.message.indexOf("uid") - 1; + String pidStr = entry.message.substring(idx0, idx1); + int pid = Integer.parseInt(pidStr); + startProc(entry.source, pid); + pidFound = true; + } catch (Exception ex) { } + + if (!pidFound) { + System.err.println("AndroidDevice: cannot find process id, console output will be disabled."); + } + } + } else if (packageName != null && !packageName.equals("") && + entry.message.contains("Killing") && + entry.message.contains(packageName)) { + // Sample message string from logcat when stopping process: + // "Killing 31360:processing.test.test1/u0a403 (adj 900): remove task" + try { + int idx0 = entry.message.indexOf("Killing") + 8; + int idx1 = entry.message.indexOf(packageName) - 1; + String pidStr = entry.message.substring(idx0, idx1); + int pid = Integer.parseInt(pidStr); + endProc(pid); + } catch (Exception ex) { + System.err.println("AndroidDevice: cannot find process id, console output will continue. " + packageName); + } } else if (entry.source.equals("Process")) { handleCrash(entry); } else if (activeProcesses.contains(entry.pid)) { @@ -235,9 +388,11 @@ private void reportStackTrace(final LogEntry entry) { void initialize() throws IOException, InterruptedException { adb("logcat", "-c"); - final String[] cmd = generateAdbCommand("logcat"); + + final String[] cmd = genAdbCommand("logcat", "-v", "brief"); final String title = PApplet.join(cmd, ' '); - logcat = Runtime.getRuntime().exec(cmd); + logcat = env.getSDK().getAdbProcess(cmd); + ProcessRegistry.watch(logcat); new StreamPump(logcat.getInputStream(), "log: " + title).addTarget( new LogLineProcessor()).start(); @@ -247,9 +402,6 @@ void initialize() throws IOException, InterruptedException { public void run() { try { logcat.waitFor(); - // final int result = logcat.waitFor(); - // System.err.println("AndroidDevice: " + getId() + " logcat exited " - // + (result == 0 ? "normally" : "with status " + result)); } catch (final InterruptedException e) { System.err .println("AndroidDevice: logcat process monitor interrupted"); @@ -314,18 +466,17 @@ public void removeListener(final DeviceListener listener) { } private ProcessResult adb(final String... cmd) throws InterruptedException, IOException { - final String[] adbCmd = generateAdbCommand(cmd); - return AndroidSDK.runADB(adbCmd); + final String[] adbCmd = genAdbCommand(cmd); + return env.getSDK().runAdb(adbCmd); + } + + private Process adbProc(final String... cmd) throws IOException { + final String[] adbCmd = genAdbCommand(cmd); + return env.getSDK().getAdbProcess(adbCmd); } - private String[] generateAdbCommand(final String... cmd) { - // final String[] adbCmd = new String[3 + cmd.length]; - // adbCmd[0] = "adb"; - // adbCmd[1] = "-s"; - // adbCmd[2] = getId(); - // System.arraycopy(cmd, 0, adbCmd, 3, cmd.length); - // return adbCmd; - return PApplet.concat(new String[] { "adb", "-s", getId() }, cmd); + private String[] genAdbCommand(final String... cmd) { + return PApplet.concat(new String[] { "-s", getId() }, cmd); } @Override diff --git a/src/processing/mode/android/Export.java b/processing/mode/src/processing/mode/android/DeviceListener.java similarity index 81% rename from src/processing/mode/android/Export.java rename to processing/mode/src/processing/mode/android/DeviceListener.java index 6ed418242..f6be233be 100644 --- a/src/processing/mode/android/Export.java +++ b/processing/mode/src/processing/mode/android/DeviceListener.java @@ -1,10 +1,9 @@ -package processing.mode.android; /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org - Copyright (c) 2009-10 Ben Fry and Casey Reas + Copyright (c) 2013-21 The Processing Foundation This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 @@ -20,3 +19,12 @@ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +package processing.mode.android; + +import java.util.List; + +public interface DeviceListener { + void stackTrace(final List trace); + + void sketchStopped(); +} diff --git a/src/processing/mode/android/Devices.java b/processing/mode/src/processing/mode/android/Devices.java similarity index 57% rename from src/processing/mode/android/Devices.java rename to processing/mode/src/processing/mode/android/Devices.java index ab1e56e39..92f1e2208 100644 --- a/src/processing/mode/android/Devices.java +++ b/processing/mode/src/processing/mode/android/Devices.java @@ -1,314 +1,399 @@ -package processing.mode.android; - -import processing.app.exec.ProcessResult; -import processing.mode.android.EmulatorController.State; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.*; - -//import processing.app.EditorConsole; - -/** - *
     AndroidEnvironment env = AndroidEnvironment.getInstance();
    - * AndroidDevice n1 = env.getHardware();
    - * AndroidDevice emu = env.getEmulator();
    - * @author Jonathan Feinberg <jdf@pobox.com> - * - */ -class Devices { - private static final String ADB_DEVICES_ERROR = - "Received unfamiliar output from “adb devices”.\n" + - "The device list may have errors."; - - private static final Devices INSTANCE = new Devices(); - - private Device selectedDevice; - - public static Devices getInstance() { - return INSTANCE; - } - - private final Map devices = - new ConcurrentHashMap(); - private final ExecutorService deviceLaunchThread = - Executors.newSingleThreadExecutor(); - - public Device getSelectedDevice() { - return selectedDevice; - } - - public void setSelectedDevice(Device selectedDevice) { - this.selectedDevice = selectedDevice; - } - - public static void killAdbServer() { - System.out.println("Shutting down any existing adb server..."); - System.out.flush(); - try { - AndroidSDK.runADB("kill-server"); - } catch (final Exception e) { - System.err.println("Devices.killAdbServer() failed."); - e.printStackTrace(); - } - } - - - private Devices() { - if (processing.app.Base.DEBUG) { - System.out.println("Starting up Devices"); - } -// killAdbServer(); - Runtime.getRuntime().addShutdownHook( - new Thread("processing.mode.android.Devices Shutdown") { - public void run() { - //System.out.println("Shutting down Devices"); - //System.out.flush(); - for (Device device : new ArrayList(devices.values())) { - device.shutdown(); - } - // Don't do this, it'll just make Eclipse and others freak out. - //killAdbServer(); - } - }); - } - - - public Future getEmulator() { - final Callable androidFinder = new Callable() { - public Device call() throws Exception { - return blockingGetEmulator(); - } - }; - final FutureTask task = - new FutureTask(androidFinder); - deviceLaunchThread.execute(task); - return task; - } - - - private final Device blockingGetEmulator() { -// System.out.println("going looking for emulator"); - Device emu = find(true); - if (emu != null) { -// System.out.println("found emu " + emu); - return emu; - } -// System.out.println("no emu found"); - - EmulatorController emuController = EmulatorController.getInstance(); -// System.out.println("checking emulator state"); - if (emuController.getState() == State.NOT_RUNNING) { - try { -// System.out.println("not running, gonna launch"); - emuController.launch(); // this blocks until emulator boots -// System.out.println("not just gonna, we've done the launch"); - } catch (final IOException e) { - System.err.println("Problem while launching emulator."); - e.printStackTrace(System.err); - return null; - } - } else { - System.out.println("Emulator is " + emuController.getState() + - ", which is not expected."); - } -// System.out.println("and now we're out"); - -// System.out.println("Devices.blockingGet thread is " + Thread.currentThread()); - while (!Thread.currentThread().isInterrupted()) { - // System.err.println("AndroidEnvironment: looking for emulator in loop."); - // System.err.println("AndroidEnvironment: emulatorcontroller state is " - // + emuController.getState()); - if (emuController.getState() == State.NOT_RUNNING) { - System.err.println("Error while starting the emulator. (" + - emuController.getState() + ")"); - return null; - } - emu = find(true); - if (emu != null) { - // System.err.println("AndroidEnvironment: returning " + emu.getId() - // + " from loop."); - return emu; - } - try { - Thread.sleep(2000); - } catch (final InterruptedException e) { - System.err.println("Devices: interrupted in loop."); - return null; - } - } - return null; - } - - - private Device find(final boolean wantEmulator) { - refresh(); - synchronized (devices) { - for (final Device device : devices.values()) { - final boolean isEmulator = device.getId().contains("emulator"); - if ((isEmulator && wantEmulator) || (!isEmulator && !wantEmulator)) { - return device; - } - } - } - return null; - } - - public List findMultiple(final boolean wantEmulator) { - List deviceList = new ArrayList(); - - refresh(); - synchronized (devices) { - for (final Device device : devices.values()) { - final boolean isEmulator = device.getId().contains("emulator"); - if ((isEmulator && wantEmulator) || (!isEmulator && !wantEmulator)) { - deviceList.add(device); - } - } - } - - return deviceList; - } - - /** - * @return the first Android hardware device known to be running, or null if there are none. - */ - public Future getHardware() { - Device device = getSelectedDevice(); - if (device == null || !device.isAlive()) device = blockingGetHardware(); - return getHardware(device); - } - - public Future getHardware(final Device device) { - final Callable androidFinder = new Callable() { - public Device call() throws Exception { - return device; - } - }; - final FutureTask task = - new FutureTask(androidFinder); - deviceLaunchThread.execute(task); - return task; - } - - private final Device blockingGetHardware() { - Device hardware = find(false); - if (hardware != null) { - return hardware; - } - while (!Thread.currentThread().isInterrupted()) { - try { - Thread.sleep(2000); - } catch (final InterruptedException e) { - return null; - } - hardware = find(false); - if (hardware != null) { - return hardware; - } - } - return null; - } - - - private void refresh() { - final List activeDevices = list(); - for (final String deviceId : activeDevices) { - if (!devices.containsKey(deviceId)) { - addDevice(new Device(this, deviceId)); - } - } - } - - - private void addDevice(final Device device) { - // System.err.println("AndroidEnvironment: adding " + device.getId()); - try { - device.initialize(); - if (devices.put(device.getId(), device) != null) { - throw new IllegalStateException("Adding " + device - + ", which already exists!"); - } - } catch (final Exception e) { - System.err.println("While initializing " + device.getId() + ": " + e); - } - } - - - void deviceRemoved(final Device device) { - // System.err.println("AndroidEnvironment: removing " + device.getId()); - if (devices.remove(device.getId()) == null) { - throw new IllegalStateException("I didn't know about device " - + device.getId() + "!"); - } - } - - - /** - *

    First line starts "List of devices" - * - *

    When an emulator is started with a debug port, then it shows up - * in the list of devices. - * - *

    List of devices attached - *
    HT91MLC00031 device - *
    emulator-5554 offline - * - *

    List of devices attached - *
    HT91MLC00031 device - *
    emulator-5554 device - * - * @return list of device identifiers - * @throws IOException - */ - public static List list() { - ProcessResult result; - try { -// System.out.println("listing devices 00"); - result = AndroidSDK.runADB("devices"); -// System.out.println("listing devices 05"); - } catch (InterruptedException e) { - return Collections.emptyList(); - } catch (IOException e) { - System.err.println("Problem inside Devices.list()"); - e.printStackTrace(); -// System.err.println(e); -// System.err.println("checking devices"); -// e.printStackTrace(EditorConsole.systemErr); - return Collections.emptyList(); - } -// System.out.println("listing devices 10"); - if (!result.succeeded()) { - if (result.getStderr().contains("protocol fault (no status)")) { - System.err.println("bleh: " + result); // THIS IS WORKING - } else { - System.err.println("nope: " + result); - } - return Collections.emptyList(); - } -// System.out.println("listing devices 20"); - - // might read "List of devices attached" - final String stdout = result.getStdout(); - if (!(stdout.startsWith("List of devices") || stdout.trim().length() == 0)) { - System.err.println(ADB_DEVICES_ERROR); - System.err.println("Output was “" + stdout + "”"); - return Collections.emptyList(); - } - -// System.out.println("listing devices 30"); - final List devices = new ArrayList(); - for (final String line : result) { - if (line.contains("\t")) { - final String[] fields = line.split("\t"); - if (fields[1].equals("device")) { - devices.add(fields[0]); - } - } - } - return devices; - } -} +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2013-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import processing.app.exec.ProcessResult; +import processing.mode.android.EmulatorController.State; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; + +//import processing.app.EditorConsole; + +/** + *

     AndroidEnvironment env = AndroidEnvironment.getInstance();
    + * AndroidDevice n1 = env.getHardware();
    + * AndroidDevice emu = env.getEmulator();
    + * @author Jonathan Feinberg <jdf@pobox.com> + * + */ +class Devices { + private static final String DEVICE_PERMISSIONS_URL = + "https://developer.android.com/studio/run/device.html"; + + private static final Devices INSTANCE = new Devices(); + + private static final String BT_DEBUG_PORT = "4444"; + + private boolean showPermissionsErrorMessage = true; + + private AndroidSDK sdk; + + private Device selectedDevice; + + public static Devices getInstance() { + return INSTANCE; + } + + private final Map devices = + new ConcurrentHashMap(); + private final ExecutorService deviceLaunchThread = + Executors.newSingleThreadExecutor(); + + public void setSDK(AndroidSDK sdk) { + this.sdk = sdk; + } + + public AndroidSDK getSDK() { + return sdk; + } + + public Device getSelectedDevice() { + return selectedDevice; + } + + public void setSelectedDevice(Device selectedDevice) { + this.selectedDevice = selectedDevice; + } + + public void killAdbServer() { + System.out.print("Shutting down any existing adb server..."); + System.out.flush(); + try { + sdk.runAdb("kill-server"); + System.out.println(" Done."); + } catch (final Exception e) { + System.err.println("/nDevices.killAdbServer() failed."); + e.printStackTrace(); + } + } + + public void startAdbServer() { + System.out.print("Starting a new adb server..."); + System.out.flush(); + try { + sdk.runAdb("start-server"); + System.out.println(" Done."); + } catch (final Exception e) { + System.err.println("/nDevices.startAdbServer() failed."); + e.printStackTrace(); + } + } + + public void enableBluetoothDebugging() { + final Devices devices = Devices.getInstance(); + java.util.List deviceList = devices.findMultiple(false); + + if (deviceList.size() != 1) { + // There is more than one non-emulator device connected to the computer, + // but don't know which one the watch could be paired to... or the watch + // is already paired to the phone, in which case we don't need to keep + // trying to connect. + return; + } + Device device = deviceList.get(0); + try { + // Try Enable debugging over bluetooth + // http://developer.android.com/training/wearables/apps/bt-debugging.html + sdk.runAdb("-s", device.getId(), "forward", "tcp:" + BT_DEBUG_PORT, + "localabstract:/adb-hub"); + sdk.runAdb("connect", "127.0.0.1:" + BT_DEBUG_PORT); + } catch (final Exception e) { + e.printStackTrace(); + } + } + + + private Devices() { + if (processing.app.Base.DEBUG) { + System.out.println("Starting up Devices"); + } +// killAdbServer(); + Runtime.getRuntime().addShutdownHook( + new Thread("processing.mode.android.Devices Shutdown") { + public void run() { + //System.out.println("Shutting down Devices"); + //System.out.flush(); + for (Device device : new ArrayList(devices.values())) { + device.shutdown(); + } + // Don't do this, it'll just make Eclipse and others freak out. + //killAdbServer(); + } + }); + } + + + public Future getEmulator(final boolean wear) { + final Callable androidFinder = new Callable() { + public Device call() throws Exception { + return blockingGetEmulator( wear); + } + }; + final FutureTask task = new FutureTask(androidFinder); + deviceLaunchThread.execute(task); + return task; + } + + + private final Device blockingGetEmulator(final boolean wear) { + String port = AVD.getPreferredPort(wear); + Device emu = find(true, port); + if (emu != null) { + return emu; + } + + EmulatorController emuController = EmulatorController.getInstance(wear); + if (emuController.getState() == State.RUNNING) { + // The emulator is in running state, but did not find any emulator device, + // to the most common cause is that it was closed, so we will re-launch it. + emuController.setState(State.NOT_RUNNING); + } + + if (emuController.getState() == State.NOT_RUNNING) { + try { + emuController.launch(sdk, wear); // this blocks until emulator boots + } catch (final IOException e) { + System.err.println("Problem while launching emulator."); + e.printStackTrace(System.err); + return null; + } + } else { + return null; + } + + while (!Thread.currentThread().isInterrupted()) { + // System.err.println("AndroidEnvironment: looking for emulator in loop."); + // System.err.println("AndroidEnvironment: emulatorcontroller state is " + // + emuController.getState()); + if (emuController.getState() == State.NOT_RUNNING) { + System.err.println("Error while starting the emulator. (" + + emuController.getState() + ")"); + return null; + } + emu = find(true, port); + if (emu != null) { + // System.err.println("AndroidEnvironment: returning " + emu.getId() + // + " from loop."); + return emu; + } + try { + Thread.sleep(2000); + } catch (final InterruptedException e) { + System.err.println("Devices: interrupted in loop."); + return null; + } + } + return null; + } + + + private Device find(final boolean wantEmulator, final String port) { + refresh(); + synchronized (devices) { + for (final Device device : devices.values()) { + if (port != null && device.getName().indexOf(port) == -1) continue; + final boolean isEmulator = device.getId().contains("emulator"); + if ((isEmulator && wantEmulator) || (!isEmulator && !wantEmulator)) { + return device; + } + } + } + return null; + } + + public List findMultiple(final boolean wantEmulator) { + List deviceList = new ArrayList(); + + refresh(); + synchronized (devices) { + for (final Device device : devices.values()) { + final boolean isEmulator = device.getId().contains("emulator"); + if ((isEmulator && wantEmulator) || (!isEmulator && !wantEmulator)) { + deviceList.add(device); + } + } + } + + return deviceList; + } + + /** + * @return the first Android hardware device known to be running, or null if there are none. + */ + public Future getHardware() { + Device device = getSelectedDevice(); + if (device == null || !device.isAlive()) device = blockingGetHardware(); + return getHardware(device); + } + + public Future getHardware(final Device device) { + final Callable androidFinder = new Callable() { + public Device call() throws Exception { + return device; + } + }; + final FutureTask task = + new FutureTask(androidFinder); + deviceLaunchThread.execute(task); + return task; + } + + private final Device blockingGetHardware() { + Device hardware = find(false, null); + if (hardware != null) { + return hardware; + } + while (!Thread.currentThread().isInterrupted()) { + try { + Thread.sleep(2000); + } catch (final InterruptedException e) { + return null; + } + hardware = find(false, null); + if (hardware != null) { + return hardware; + } + } + return null; + } + + + private void refresh() { + final List activeDevices = list(); + for (final String deviceId : activeDevices) { + if (!devices.containsKey(deviceId)) { + Device device = new Device(this, deviceId); + addDevice(device); + } + } + } + + + private void addDevice(final Device device) { + // System.err.println("AndroidEnvironment: adding " + device.getId()); + try { + device.initialize(); + if (devices.put(device.getId(), device) != null) { + // TODO: Silently add existing device, this may be indicating, like in + // deviceRemoved() below, different threads trying to add the same + // device. Does not seem to have any negative effect. +// throw new IllegalStateException("Adding " + device +// + ", which already exists!"); + } + } catch (final Exception e) { + System.err.println("While initializing " + device.getId() + ": " + e); + } + } + + + void deviceRemoved(final Device device) { + String id = device.getId(); + if (devices.containsKey(id)) { + devices.remove(device.getId()); + } else { + // TODO: Device already removed, don't throw exception as this seems to + // happen quite often when removing a device, perhaps shutdown() gets + // called twice? +// throw new IllegalStateException("I didn't know about device " +// + device.getId() + "!"); + } + } + + + /** + *

    First line starts "List of devices" + * + *

    When an emulator is started with a debug port, then it shows up + * in the list of devices. + * + *

    List of devices attached + *
    HT91MLC00031 device + *
    emulator-5554 offline + * + *

    List of devices attached + *
    HT91MLC00031 device + *
    emulator-5554 device + * + * @return list of device identifiers + * @throws IOException + */ + public List list() { + if (AndroidSDK.adbDisabled) { + return Collections.emptyList(); + } + + ProcessResult result; + try { +// System.out.println("listing devices 00"); + result = sdk.runAdb("devices"); +// System.out.println("listing devices 05"); + } catch (InterruptedException e) { + return Collections.emptyList(); + } catch (IOException e) { + System.err.println("Problem inside Devices.list()"); + e.printStackTrace(); +// System.err.println(e); +// System.err.println("checking devices"); +// e.printStackTrace(EditorConsole.systemErr); + return Collections.emptyList(); + } +// System.out.println("listing devices 10"); + if (!result.succeeded()) { + if (result.getStderr().contains("protocol fault (no status)")) { + System.err.println("bleh: " + result); // THIS IS WORKING + } else { + System.err.println("nope: " + result); + } + return Collections.emptyList(); + } +// System.out.println("listing devices 20"); + + // might read "List of devices attached" + final String stdout = result.getStdout(); + if (!(stdout.contains("List of devices") || stdout.trim().length() == 0)) { + System.err.println(AndroidMode.getTextString("android_devices.error.cannot_get_device_list")); + System.err.println(stdout); + return Collections.emptyList(); + } + +// System.out.println("listing devices 30"); + final List devices = new ArrayList(); + for (final String line : result) { + if (line.contains("\t")) { + final String[] fields = line.split("\t"); + if (fields[1].equals("device")) { + devices.add(fields[0]); + } else if (fields[1].contains("no permissions") && showPermissionsErrorMessage) { + AndroidUtil.showMessage(AndroidMode.getTextString("android_devices.error.no_permissions_title"), + AndroidMode.getTextString("android_devices.error.no_permissions_body", DEVICE_PERMISSIONS_URL)); + showPermissionsErrorMessage = false; + } + } + } + return devices; + } +} diff --git a/src/processing/mode/android/EmulatorController.java b/processing/mode/src/processing/mode/android/EmulatorController.java similarity index 70% rename from src/processing/mode/android/EmulatorController.java rename to processing/mode/src/processing/mode/android/EmulatorController.java index 3692ace19..e3d9b7a13 100644 --- a/src/processing/mode/android/EmulatorController.java +++ b/processing/mode/src/processing/mode/android/EmulatorController.java @@ -1,10 +1,31 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2013-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + package processing.mode.android; +import java.io.File; import java.io.IOException; import java.util.concurrent.CountDownLatch; import processing.app.Base; -import processing.app.Preferences; import processing.app.exec.*; import processing.core.PApplet; @@ -23,7 +44,7 @@ public State getState() { } - private void setState(final State state) { + public void setState(final State state) { if (processing.app.Base.DEBUG) { //System.out.println("Emulator state: " + state); new Exception("setState(" + state + ") called").printStackTrace(System.out); @@ -36,27 +57,36 @@ private void setState(final State state) { * Blocks until emulator is running, or some catastrophe happens. * @throws IOException */ - synchronized public void launch() throws IOException { + synchronized public void launch(final AndroidSDK sdk, final boolean wear) + throws IOException { if (state != State.NOT_RUNNING) { String illegal = "You can't launch an emulator whose state is " + state; throw new IllegalStateException(illegal); } - String portString = Preferences.get("android.emulator.port"); - if (portString == null) { - portString = "5566"; - Preferences.set("android.emulator.port", portString); + // Emulator options: + // https://developer.android.com/studio/run/emulator-commandline.html + String avdName = AVD.getName(wear); + + final String portString = AVD.getPreferredPort(wear); + + // We let the emulator decide what's better for hardware acceleration: + // https://developer.android.com/studio/run/emulator-acceleration.html#accel-graphics + String gpuFlag = "auto"; + + final File emulator = sdk.getEmulatorTool(); + if (emulator == null || !emulator.exists()) { + System.err.println("EmulatorController: Emulator is not available."); + return; } - - // See http://developer.android.com/guide/developing/tools/emulator.html + final String[] cmd = new String[] { - "emulator", - "-avd", AVD.defaultAVD.name, + emulator.getCanonicalPath(), + "-avd", avdName, "-port", portString, -// "-no-boot-anim", // does this do anything? - // http://code.google.com/p/processing/issues/detail?id=1059 -// "-gpu", "on" // enable OpenGL + "-gpu", gpuFlag }; + //System.err.println("EmulatorController: Launching emulator"); if (Base.DEBUG) { System.out.println(processing.core.PApplet.join(cmd, " ")); @@ -116,12 +146,11 @@ public void run() { } Thread.sleep(2000); //System.out.println("done sleeping"); - for (final String device : Devices.list()) { - if (device.contains("emulator")) { - //System.err.println("EmulatorController: Emulator booted."); - setState(State.RUNNING); - return; - } + ProcessResult result = sdk.runAdb("-s", "emulator-" + portString, + "shell", "getprop", "dev.bootcomplete"); + if (result.getStdout().trim().equals("1")) { + setState(State.RUNNING); + return; } } System.err.println("EmulatorController: Emulator never booted. " + state); @@ -167,9 +196,14 @@ public void run() { // whoever called them "design patterns" certainly wasn't a f*king designer. - public static EmulatorController getInstance() { - return INSTANCE; + public static EmulatorController getInstance(boolean wear) { + if (wear) { + return INSTANCE_WEAR; + } else { + return INSTANCE; + } } private static final EmulatorController INSTANCE = new EmulatorController(); + private static final EmulatorController INSTANCE_WEAR = new EmulatorController(); } diff --git a/src/processing/mode/android/KeyStoreManager.java b/processing/mode/src/processing/mode/android/KeyStoreManager.java similarity index 54% rename from src/processing/mode/android/KeyStoreManager.java rename to processing/mode/src/processing/mode/android/KeyStoreManager.java index 22db61415..d968687ca 100644 --- a/src/processing/mode/android/KeyStoreManager.java +++ b/processing/mode/src/processing/mode/android/KeyStoreManager.java @@ -1,7 +1,30 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2014-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + package processing.mode.android; -import processing.app.Base; -import processing.app.Preferences; +import processing.app.Language; +import processing.app.Messages; +import processing.app.Platform; +import processing.app.ui.Toolkit; import javax.swing.*; import javax.swing.border.EmptyBorder; @@ -19,9 +42,18 @@ @SuppressWarnings("serial") public class KeyStoreManager extends JFrame { + final static protected int PACKAGE = 0; + final static protected int BUNDLE = 1; + + final static private int BOX_BORDER = Toolkit.zoom(13); + final static private int PASS_BORDER = Toolkit.zoom(15); + final static private int LABEL_WIDTH = Toolkit.zoom(400); + final static private int LABEL_HEIGHT = Toolkit.zoom(100); + final static private int GAP = Toolkit.zoom(13); + static final String GUIDE_URL = - "http://developer.android.com/tools/publishing/app-signing.html#cert"; - + "https://developer.android.com/studio/publish/app-signing.html"; + File keyStore; AndroidEditor editor; @@ -35,34 +67,36 @@ public class KeyStoreManager extends JFrame { JTextField country; JTextField stateName; - public KeyStoreManager(final AndroidEditor editor) { + public KeyStoreManager(final AndroidEditor editor, final int kind) { super("Android keystore manager"); this.editor = editor; - createLayout(); + createLayout(kind); } - private void createLayout() { + private void createLayout(int kind) { Container outer = getContentPane(); outer.removeAll(); - Box pain = Box.createVerticalBox(); - pain.setBorder(new EmptyBorder(13, 13, 13, 13)); - outer.add(pain); + Box vbox = Box.createVerticalBox(); + vbox.setBorder(new EmptyBorder(BOX_BORDER, BOX_BORDER, BOX_BORDER, BOX_BORDER)); + outer.add(vbox); keyStore = AndroidKeyStore.getKeyStore(); if (keyStore != null) { - showKeystorePasswordLayout(pain); + showKeystorePasswordLayout(vbox); } else { - showKeystoreCredentialsLayout(pain); + showKeystoreCredentialsLayout(vbox); } + vbox.add(Box.createVerticalStrut(GAP)); + // buttons JPanel buttons = new JPanel(); buttons.setAlignmentX(LEFT_ALIGNMENT); - JButton okButton = new JButton("OK"); - Dimension dim = new Dimension(Preferences.BUTTON_WIDTH, - okButton.getPreferredSize().height); + JButton okButton = new JButton(Language.text("prompt.ok")); + Dimension dim = new Dimension(Toolkit.getButtonWidth(), + okButton.getPreferredSize().height); okButton.setPreferredSize(dim); okButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -74,20 +108,28 @@ public void actionPerformed(ActionEvent e) { localityName.getText(), stateName.getText(), country.getText()); setVisible(false); - editor.startExportPackage(new String(passwordField.getPassword())); + if (kind == KeyStoreManager.BUNDLE) { + editor.startExportBundle(new String(passwordField.getPassword())); + } else { + editor.startExportPackage(new String(passwordField.getPassword())); + } } catch (Exception e1) { e1.printStackTrace(); } } else { setVisible(false); - editor.startExportPackage(new String(passwordField.getPassword())); + if (kind == KeyStoreManager.BUNDLE) { + editor.startExportBundle(new String(passwordField.getPassword())); + } else { + editor.startExportPackage(new String(passwordField.getPassword())); + } } } } }); okButton.setEnabled(true); - JButton cancelButton = new JButton("Cancel"); + JButton cancelButton = new JButton(Language.text("prompt.cancel")); cancelButton.setPreferredSize(dim); cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -96,27 +138,27 @@ public void actionPerformed(ActionEvent e) { }); cancelButton.setEnabled(true); - JButton resetKeystoreButton = new JButton("Reset password"); - dim = new Dimension(Preferences.BUTTON_WIDTH*2, - okButton.getPreferredSize().height); + JButton resetKeystoreButton = new JButton(AndroidMode.getTextString("keystore_manager.reset_password")); + dim = new Dimension(Toolkit.getButtonWidth()*2, + resetKeystoreButton.getPreferredSize().height); resetKeystoreButton.setPreferredSize(dim); resetKeystoreButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setVisible(false); - int result = Base.showYesNoQuestion(editor, "Android keystore", - "Are you sure you want to reset the password?", "We will have to reset the keystore to do this, " + - "which means you won't be able to upload an update for your app signed with the new keystore to Google Play.

    " + - "We will make a backup for the old keystore."); + int result = Messages.showYesNoQuestion(editor, AndroidMode.getTextString("keystore_manager.dialog.reset_keyboard_title"), + AndroidMode.getTextString("keystore_manager.dialog.reset_keyboard_body_part1"), + AndroidMode.getTextString("keystore_manager.dialog.reset_keyboard_body_part2")); if (result == JOptionPane.NO_OPTION) { setVisible(true); } else { if (!AndroidKeyStore.resetKeyStore()) { - Base.showWarning("Android keystore", "Failed to remove keystore"); + Messages.showWarning(AndroidMode.getTextString("keystore_manager.warn.cannot_remove_keystore_title"), + AndroidMode.getTextString("keystore_manager.warn.cannot_remove_keystore_body")); setVisible(true); } else { keyStore = null; - createLayout(); + createLayout(kind); } } } @@ -124,7 +166,7 @@ public void actionPerformed(ActionEvent e) { resetKeystoreButton.setEnabled(true); // think different, biznatchios! - if (Base.isMacOS()) { + if (Platform.isMacOS()) { buttons.add(cancelButton); if (keyStore != null) buttons.add(resetKeystoreButton); @@ -138,7 +180,7 @@ public void actionPerformed(ActionEvent e) { buttons.add(cancelButton); } // buttons.setMaximumSize(new Dimension(300, buttons.getPreferredSize().height)); - pain.add(buttons); + vbox.add(buttons); JRootPane root = getRootPane(); root.setDefaultButton(okButton); @@ -147,23 +189,23 @@ public void actionPerformed(ActionEvent actionEvent) { setVisible(false); } }; - processing.app.Toolkit.registerWindowCloseKeys(root, disposer); - processing.app.Toolkit.setIcon(this); + Toolkit.registerWindowCloseKeys(root, disposer); + Toolkit.setIcon(this); pack(); - - Dimension screen = processing.app.Toolkit.getScreenSize(); + /* + Dimension screen = Toolkit.getScreenSize(); Dimension windowSize = getSize(); - setLocation((screen.width - windowSize.width) / 2, (screen.height - windowSize.height) / 2); - + */ + setLocationRelativeTo(null); setVisible(true); } private void showKeystorePasswordLayout(Box pain) { passwordField = new JPasswordField(15); - JLabel passwordLabel = new JLabel("Keystore password: "); + JLabel passwordLabel = new JLabel("" + AndroidMode.getTextString("keystore_manager.password_label") + " "); passwordLabel.setLabelFor(passwordField); JPanel textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); @@ -180,126 +222,122 @@ private boolean checkRequiredFields() { if (Arrays.equals(passwordField.getPassword(), repeatPasswordField.getPassword())) { return true; } else { - Base.showWarning("Passwords", "Keystore passwords do not match"); + Messages.showWarning(AndroidMode.getTextString("keystore_manager.warn.password_missmatch_title"), + AndroidMode.getTextString("keystore_manager.warn.password_missmatch_body")); return false; } } else { - Base.showWarning("Passwords", "Keystore password should be at least 6 characters long"); + Messages.showWarning(AndroidMode.getTextString("keystore_manager.warn.short_password_title"), + AndroidMode.getTextString("keystore_manager.warn.short_password_body")); return false; } } - private void showKeystoreCredentialsLayout(Box pain) { - String labelText = - "" + - "Please enter the information below so we can generate a private key for you.
    " + - "Fields marked bold are required, " + - "though you may consider to fill some of optional fields below those to avoid potential problems.
    " + - "More about private keys can be found " + - "here."; + private void showKeystoreCredentialsLayout(Box box) { + String labelText = AndroidMode.getTextString("keystore_manager.top_label"); JLabel textarea = new JLabel(labelText); - textarea.setPreferredSize(new Dimension(400, 100)); + textarea.setPreferredSize(new Dimension(LABEL_WIDTH, LABEL_HEIGHT)); textarea.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { - Base.openURL(GUIDE_URL); + Platform.openURL(GUIDE_URL); } }); textarea.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textarea); + box.add(textarea); // password field passwordField = new JPasswordField(15); - JLabel passwordLabel = new JLabel("Keystore password: "); + JLabel passwordLabel = new JLabel("" + AndroidMode.getTextString("keystore_manager.password_label") + " "); passwordLabel.setLabelFor(passwordField); JPanel textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(passwordLabel); textPane.add(passwordField); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); // repeat password field repeatPasswordField = new JPasswordField(15); - JLabel repeatPasswordLabel = new JLabel("Repeat keystore password: "); + JLabel repeatPasswordLabel = new JLabel("" + AndroidMode.getTextString("keystore_manager.repeat_password_label") + " "); repeatPasswordLabel.setLabelFor(passwordField); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(repeatPasswordLabel); textPane.add(repeatPasswordField); textPane.setAlignmentX(LEFT_ALIGNMENT); - textPane.setBorder(new EmptyBorder(0, 0, 15, 0)); - pain.add(textPane); + textPane.setBorder(new EmptyBorder(0, 0, PASS_BORDER, 0)); + box.add(textPane); MatteBorder mb = new MatteBorder(1, 0, 0, 0, Color.LIGHT_GRAY); - TitledBorder tb = new TitledBorder(mb, "Keystore issuer credentials", TitledBorder.LEFT, TitledBorder.DEFAULT_POSITION); + TitledBorder tb = new TitledBorder(mb, AndroidMode.getTextString("keystore_manager.issuer_credentials_header"), TitledBorder.LEFT, TitledBorder.DEFAULT_POSITION); JPanel separatorPanel = new JPanel(); separatorPanel.setBorder(tb); - pain.add(separatorPanel); + box.add(separatorPanel); // common name (CN) commonName = new JTextField(15); - JLabel commonNameLabel = new JLabel("First and last name: "); + JLabel commonNameLabel = new JLabel(AndroidMode.getTextString("keystore_manager.common_name_label")); commonNameLabel.setLabelFor(commonName); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(commonNameLabel); textPane.add(commonName); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); // organizational unit (OU) organizationalUnit = new JTextField(15); - JLabel organizationalUnitLabel = new JLabel("Organizational unit: "); + JLabel organizationalUnitLabel = new JLabel(AndroidMode.getTextString("keystore_manager.organizational_unitl_label")); organizationalUnitLabel.setLabelFor(organizationalUnit); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(organizationalUnitLabel); textPane.add(organizationalUnit); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); // organization name (O) organizationName = new JTextField(15); - JLabel organizationNameLabel = new JLabel("Organization name: "); + JLabel organizationNameLabel = new JLabel(AndroidMode.getTextString("keystore_manager.organization_name_label")); organizationNameLabel.setLabelFor(organizationName); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(organizationNameLabel); textPane.add(organizationName); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); // locality name (L) localityName = new JTextField(15); - JLabel localityNameLabel = new JLabel("City or locality: "); + JLabel localityNameLabel = new JLabel(AndroidMode.getTextString("keystore_manager.city_name_label")); localityNameLabel.setLabelFor(localityName); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(localityNameLabel); textPane.add(localityName); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); // state name (S) stateName = new JTextField(15); - JLabel stateNameLabel = new JLabel("State name: "); + JLabel stateNameLabel = new JLabel(AndroidMode.getTextString("keystore_manager.state_name_label")); stateNameLabel.setLabelFor(stateName); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(stateNameLabel); textPane.add(stateName); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); // country (C) country = new JTextField(15); - JLabel countryLabel = new JLabel("Country code (XX): "); + JLabel countryLabel = new JLabel(AndroidMode.getTextString("keystore_manager.country_code_label")); countryLabel.setLabelFor(country); textPane = new JPanel(new FlowLayout(FlowLayout.TRAILING)); textPane.add(countryLabel); textPane.add(country); textPane.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textPane); + box.add(textPane); } } diff --git a/src/processing/mode/android/Keys.java b/processing/mode/src/processing/mode/android/Keys.java similarity index 84% rename from src/processing/mode/android/Keys.java rename to processing/mode/src/processing/mode/android/Keys.java index 77b5fb3b0..02b796227 100644 --- a/src/processing/mode/android/Keys.java +++ b/processing/mode/src/processing/mode/android/Keys.java @@ -3,7 +3,8 @@ /* Part of the Processing project - http://processing.org - Copyright (c) 2010 Ben Fry and Casey Reas + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2010-12 Ben Fry and Casey Reas This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 @@ -21,14 +22,10 @@ package processing.mode.android; -//import java.awt.*; -//import java.awt.event.*; -//import java.util.ArrayList; -//import java.util.HashMap; - import javax.swing.*; -import processing.app.*; +import processing.app.ui.Editor; + @SuppressWarnings("serial") public class Keys extends JFrame { diff --git a/src/processing/mode/android/LogEntry.java b/processing/mode/src/processing/mode/android/LogEntry.java similarity index 66% rename from src/processing/mode/android/LogEntry.java rename to processing/mode/src/processing/mode/android/LogEntry.java index b88eaa2f8..e4b0fd51c 100644 --- a/src/processing/mode/android/LogEntry.java +++ b/processing/mode/src/processing/mode/android/LogEntry.java @@ -1,3 +1,24 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2013-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + package processing.mode.android; import java.util.regex.Matcher; diff --git a/processing/mode/src/processing/mode/android/Manifest.java b/processing/mode/src/processing/mode/android/Manifest.java new file mode 100644 index 000000000..1cc755f86 --- /dev/null +++ b/processing/mode/src/processing/mode/android/Manifest.java @@ -0,0 +1,446 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2010-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package processing.mode.android; + +import org.xml.sax.SAXException; +import processing.app.Messages; +import processing.app.Sketch; +import processing.core.PApplet; +import processing.data.XML; + +import javax.xml.parsers.ParserConfigurationException; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.HashMap; + +/** + * Class encapsulating the manifest file associated with a Processing sketch + * in the Android mode. + * + */ +public class Manifest { + static final String MANIFEST_XML = "AndroidManifest.xml"; + + static final String MANIFEST_ERROR_TITLE = "Error handling " + MANIFEST_XML; + static final String MANIFEST_ERROR_MESSAGE = + "Errors occurred while reading or writing " + MANIFEST_XML + ",\n" + + "which means lots of things are likely to stop working properly.\n" + + "To prevent losing any data, it's recommended that you use “Save As”\n" + + "to save a separate copy of your sketch, and then restart Processing."; + + static private final String[] MANIFEST_TEMPLATE = { + "AppManifest.xml.tmpl", + "WallpaperManifest.xml.tmpl", + "WatchFaceManifest.xml.tmpl", + "VRManifest.xml.tmpl", + "ARManifest.xml.tmpl" + }; + + // Default base package name, user need to change when exporting package. + static final String BASE_PACKAGE = "processing.test"; + + static final String PERMISSION_PREFIX = "android.permission."; + + private Sketch sketch; + private int appComp; + private File modeFolder; + + /** the manifest data read from the file */ + private XML xml; + + + public Manifest(Sketch sketch, int appComp, File modeFolder, boolean forceNew) { + this.sketch = sketch; + this.appComp = appComp; + this.modeFolder = modeFolder; + load(forceNew); + } + + + private String defaultPackageName() { + return BASE_PACKAGE + "." + sketch.getName().toLowerCase(); + } + + + private String defaultVersionCode() { + return "1"; + } + + + private String defaultVersionName() { + return "1.0"; + } + + + // called by other classes who want an actual package name + // internally, we'll figure this out ourselves whether it's filled or not + public String getPackageName() { + String pkg = xml.getString("package"); + return pkg.length() == 0 ? defaultPackageName() : pkg; + } + + + public String getVersionCode() { + String code = xml.getString("android:versionCode"); + return code.length() == 0 ? defaultVersionCode() : code; + } + + + public String getVersionName() { + String name = xml.getString("android:versionName"); + return name.length() == 0 ? defaultVersionName() : name; + } + + + public void setPackageName(String packageName) { + xml.setString("package", packageName); + save(); + } + + + public String[] getPermissions() { + XML[] elements = xml.getChildren("uses-permission"); + int count = elements.length; + String[] names = new String[count]; + for (int i = 0; i < count; i++) { + String tmp = elements[i].getString("android:name"); + if (tmp.indexOf("android.permission") == 0) { + // Standard permission, remove prefix + int idx = tmp.lastIndexOf("."); + names[i] = tmp.substring(idx + 1); + } else { + // Non-standard permission (for example, wearables) + // Store entire name. + names[i] = tmp; + } + } + return names; + } + + + public void setPermissions(String[] names) { + boolean hasWakeLock = false; + boolean hasVibrate = false; + boolean hasReadExtStorage = false; + boolean hasCameraAccess = false; + + // Remove all the old permissions... + for (XML kid : xml.getChildren("uses-permission")) { + String name = kid.getString("android:name"); + + // ...except the ones for watch faces and VR apps. + if (appComp == AndroidBuild.WATCHFACE && name.equals(PERMISSION_PREFIX + "WAKE_LOCK")) { + hasWakeLock = true; + continue; + } + if (appComp == AndroidBuild.VR && name.equals(PERMISSION_PREFIX + "VIBRATE")) { + hasVibrate = true; + continue; + } + if (appComp == AndroidBuild.VR && name.equals(PERMISSION_PREFIX + "READ_EXTERNAL_STORAGE")) { + hasReadExtStorage = true; + continue; + } + if (appComp == AndroidBuild.AR && name.equals(PERMISSION_PREFIX + "CAMERA")) { + hasCameraAccess = true; + continue; + } + + // Don't remove non-standard permissions, such as + // com.google.android.wearable.permission.RECEIVE_COMPLICATION_DATA + // because these are set manually by the user. + if (-1 < name.indexOf("com.google.android")) continue; + xml.removeChild(kid); + } + + // ...and add the new permissions back + for (String name : names) { + + // Don't add required permissions for watch faces and VR again... + if (appComp == AndroidBuild.WATCHFACE && name.equals("WAKE_LOCK")) continue; + if (appComp == AndroidBuild.VR && name.equals("VIBRATE")) continue; + if (appComp == AndroidBuild.VR && name.equals("READ_EXTERNAL_STORAGE")) continue; + if (appComp == AndroidBuild.AR && name.equals(PERMISSION_PREFIX + "CAMERA")) continue; + + XML newbie = xml.addChild("uses-permission"); + if (-1 < name.indexOf(".")) { + // Permission string contains path + newbie.setString("android:name", name); + } else { + newbie.setString("android:name", PERMISSION_PREFIX + name); + } + } + + // ...unless they were initially missing. + if (appComp == AndroidBuild.WATCHFACE && !hasWakeLock) { + xml.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "WAKE_LOCK"); + } + if (appComp == AndroidBuild.VR && !hasVibrate) { + xml.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "VIBRATE"); + } + if (appComp == AndroidBuild.VR && !hasReadExtStorage) { + xml.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "READ_EXTERNAL_STORAGE"); + } + if (appComp == AndroidBuild.AR && !hasCameraAccess) { + xml.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "CAMERA"); + } + + save(); + } + + + private void fixPermissions(XML mf) { + boolean hasWakeLock = false; + boolean hasVibrate = false; + boolean hasReadExtStorage = false; + boolean hasCameraAccess = false; + for (XML kid : mf.getChildren("uses-permission")) { + String name = kid.getString("android:name"); + if (appComp == AndroidBuild.WATCHFACE && name.equals(PERMISSION_PREFIX + "WAKE_LOCK")) { + hasWakeLock = true; + continue; + } + if (appComp == AndroidBuild.VR && name.equals(PERMISSION_PREFIX + "VIBRATE")) { + hasVibrate = true; + continue; + } + if (appComp == AndroidBuild.VR && name.equals(PERMISSION_PREFIX + "READ_EXTERNAL_STORAGE")) { + hasReadExtStorage = true; + continue; + } + if (appComp == AndroidBuild.AR && name.equals(PERMISSION_PREFIX + "CAMERA")) { + hasCameraAccess = true; + continue; + } + + if (appComp == AndroidBuild.AR && !hasCameraAccess) { + mf.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "CAMERA"); + } + } + if (appComp == AndroidBuild.WATCHFACE && !hasWakeLock) { + mf.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "WAKE_LOCK"); + } + if (appComp == AndroidBuild.VR && !hasVibrate) { + mf.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "VIBRATE"); + } + if (appComp == AndroidBuild.VR && !hasReadExtStorage) { + mf.addChild("uses-permission"). + setString("android:name", PERMISSION_PREFIX + "READ_EXTERNAL_STORAGE"); + } + } + + + private void writeBlankManifest(final File xmlFile, final int appComp) { + File xmlTemplate = new File(modeFolder, "templates/" + MANIFEST_TEMPLATE[appComp]); + HashMap replaceMap = new HashMap(); + AndroidUtil.createFileFromTemplate(xmlTemplate, xmlFile, replaceMap); + } + + + /** + * Save a new version of the manifest info to the build location. + * Also fill in any missing attributes that aren't yet set properly. + */ + protected void writeCopy(File file, String className) throws IOException { + // write a copy to the build location + save(file); + + // load the copy from the build location and start messing with it + XML mf = null; + try { + mf = new XML(file); + + // package name, or default + String p = mf.getString("package").trim(); + if (p.length() == 0) { + mf.setString("package", defaultPackageName()); + } + + // app name and label, or the class name + XML app = mf.getChild("application"); + String label = app.getString("android:label"); + if (label.length() == 0) { + app.setString("android:label", className); + } + + // Services need the label also in the service section + if (appComp == AndroidBuild.WALLPAPER || appComp == AndroidBuild.WATCHFACE) { + XML serv = app.getChild("service"); + label = serv.getString("android:label"); + if (label.length() == 0) { + serv.setString("android:label", className); + } + } + + // Make sure that the required permissions for watch faces, AR and VR apps are + // included. + if (appComp == AndroidBuild.WATCHFACE || appComp == AndroidBuild.VR|| appComp == AndroidBuild.AR) { + fixPermissions(mf); + } + + PrintWriter writer = PApplet.createWriter(file); + writer.print(mf.format(4)); + writer.flush(); + writer.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + + protected void load(boolean forceNew) { + File manifestFile = getManifestFile(); + if (manifestFile.exists()) { + try { + xml = new XML(manifestFile); + + boolean saveOld = false; + + XML app = xml.getChild("application"); + String icon = app.getString("android:icon"); + if (icon.equals("@drawable/icon")) { + // Manifest file generated with older version of the mode, replace icon and save + app.setString("android:icon", "@mipmap/ic_launcher"); + saveOld = true; + } + + XML activity = app.getChild("activity"); + XML service = app.getChild("service"); + if (activity != null && activity.getString("android:name").equals(".MainActivity")) { + addExportedAttrib(activity); + saveOld = true; + } + if (service != null && service.getString("android:name").equals(".MainService")) { + addExportedAttrib(service); + saveOld = true; + } + + XML usesSDK = xml.getChild("uses-sdk"); + if (usesSDK != null) { + // Manifest file generated with older version of the mode, uses-sdk is no longer needed in manifest + xml.removeChild(usesSDK); + saveOld = true; + } + + if (saveOld && !forceNew) save(); + + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Problem reading AndroidManifest.xml, creating a new version"); + + // remove the old manifest file, rename it with date stamp + long lastModified = manifestFile.lastModified(); + String stamp = AndroidMode.getDateStamp(lastModified); + File dest = new File(sketch.getFolder(), MANIFEST_XML + "." + stamp); + boolean moved = manifestFile.renameTo(dest); + if (!moved) { + System.err.println("Could not move/rename " + manifestFile.getAbsolutePath()); + System.err.println("You'll have to move or remove it before continuing."); + return; + } + } + } + + String[] permissionNames = null; + String pkgName = null; + String versionCode = null; + String versionName = null; + if (xml != null && forceNew) { + permissionNames = getPermissions(); + pkgName = getPackageName(); + versionCode = getVersionCode(); + versionName = getVersionName(); + xml = null; + } + + if (xml == null) { + writeBlankManifest(manifestFile, appComp); + try { + xml = new XML(manifestFile); + if (permissionNames != null) { + setPermissions(permissionNames); + } + if (pkgName != null) { + xml.setString("package", pkgName); + } + if (versionCode != null) { + xml.setString("android:versionCode", versionCode); + } + if (versionName != null) { + xml.setString("android:versionName", versionName); + } + } catch (FileNotFoundException e) { + System.err.println("Could not read " + manifestFile.getAbsolutePath()); + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } + } + if (xml == null) { + Messages.showWarning(AndroidMode.getTextString("manifest.warn.cannot_handle_file_title", MANIFEST_XML), + AndroidMode.getTextString("manifest.warn.cannot_handle_file_body", MANIFEST_XML)); + } + } + + protected void addExportedAttrib(XML child) { + if (!child.hasAttribute("android:exported")) { + // Manifest file generated with older version of the mode, missing android:exported attributed + child.setString("android:exported", "true"); + } + } + + protected void save() { + save(getManifestFile()); + } + + + /** + * Save to the sketch folder, so that it can be copied in later. + */ + protected void save(File file) { + PrintWriter writer = PApplet.createWriter(file); +// xml.write(writer); + writer.print(xml.format(4)); + writer.flush(); + writer.close(); + } + + + private File getManifestFile() { + return new File(sketch.getFolder(), MANIFEST_XML); + } +} diff --git a/processing/mode/src/processing/mode/android/Pair.java b/processing/mode/src/processing/mode/android/Pair.java new file mode 100644 index 000000000..04ef7ea81 --- /dev/null +++ b/processing/mode/src/processing/mode/android/Pair.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package processing.mode.android; + +/** + * Pair of two elements. + */ +public final class Pair { + private final A mFirst; + private final B mSecond; + private Pair(A first, B second) { + mFirst = first; + mSecond = second; + } + public static Pair create(A first, B second) { + return new Pair(first, second); + } + public A getFirst() { + return mFirst; + } + public B getSecond() { + return mSecond; + } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode()); + result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + @SuppressWarnings("rawtypes") + Pair other = (Pair) obj; + if (mFirst == null) { + if (other.mFirst != null) { + return false; + } + } else if (!mFirst.equals(other.mFirst)) { + return false; + } + if (mSecond == null) { + if (other.mSecond != null) { + return false; + } + } else if (!mSecond.equals(other.mSecond)) { + return false; + } + return true; + } +} \ No newline at end of file diff --git a/processing/mode/src/processing/mode/android/Permissions.java b/processing/mode/src/processing/mode/android/Permissions.java new file mode 100644 index 000000000..10b80f98f --- /dev/null +++ b/processing/mode/src/processing/mode/android/Permissions.java @@ -0,0 +1,683 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2012-21 The Processing Foundation + Copyright (c) 2010-12 Ben Fry and Casey Reas + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.mode.android; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.event.*; +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; + +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; + +import processing.app.Language; +import processing.app.Platform; +import processing.app.Sketch; +import processing.app.ui.Toolkit; + + +@SuppressWarnings("serial") +public class Permissions extends JFrame { + static final String GUIDE_URL = + "https://developer.android.com/training/articles/security-tips.html#Permissions"; + + static final int BORDER_HORIZ = Toolkit.zoom(5); + static final int BORDER_VERT = Toolkit.zoom(3); + static final int CELL_HEIGHT = Toolkit.zoom(20); + static final int BORDER = Toolkit.zoom(13); + static final int TEXT_WIDTH = Toolkit.zoom(400); + static final int TEXT_HEIGHT = Toolkit.zoom(80); + static final int URL_WIDTH = Toolkit.zoom(400); + static final int URL_HEIGHT = Toolkit.zoom(30); + static final int DESC_WIDTH = Toolkit.zoom(400); + static final int DESC_HEIGHT = Toolkit.zoom(50); + static final int GAP = Toolkit.zoom(8); + + JScrollPane permissionScroller; + JList permissionList; + JLabel descriptionLabel; + Sketch sketch; + + int appComp; + + File modeFolder; + + + public Permissions(Sketch sketch, int appComp, File modeFolder) { + super("Android Permissions Selector"); + this.appComp = appComp; + this.sketch = sketch; + this.modeFolder = modeFolder; + + permissionList = new CheckBoxList(); + permissionList.addListSelectionListener(new ListSelectionListener() { + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting() == false) { + int index = permissionList.getSelectedIndex(); + if (index == -1) { + descriptionLabel.setText(""); + } else { + descriptionLabel.setText("" + description[index] + ""); + } + } + } + }); + permissionList.setFixedCellHeight(CELL_HEIGHT); + permissionList.setBorder(new EmptyBorder(BORDER_VERT, BORDER_HORIZ, + BORDER_VERT, BORDER_HORIZ)); + + DefaultListModel model = new DefaultListModel(); + permissionList.setModel(model); + for (String item : title) { + model.addElement(new JCheckBox(item)); + } + + permissionScroller = + new JScrollPane(permissionList, + ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + permissionList.setVisibleRowCount(12); + permissionList.addKeyListener(new KeyAdapter() { + public void keyTyped(KeyEvent e) { + if (e.getKeyChar() == ' ') { + int index = permissionList.getSelectedIndex(); + JCheckBox checkbox = + permissionList.getModel().getElementAt(index); + checkbox.setSelected(!checkbox.isSelected()); + permissionList.repaint(); + } + } + }); + + Container outer = getContentPane(); + Box vbox = Box.createVerticalBox(); + vbox.setBorder(new EmptyBorder(BORDER, BORDER, BORDER, BORDER)); + outer.add(vbox); + + String labelText = AndroidMode.getTextString("permissions.dialog.label"); + String urlText = AndroidMode.getTextString("permissions.dialog.url", GUIDE_URL); + JLabel textarea = new JLabel(labelText); + JLabel urlarea = new JLabel(urlText); + textarea.setPreferredSize(new Dimension(TEXT_WIDTH, TEXT_HEIGHT)); + urlarea.setPreferredSize(new Dimension(URL_WIDTH, URL_HEIGHT)); + urlarea.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + Platform.openURL(GUIDE_URL); + } + }); + urlarea.setCursor(new java.awt.Cursor(java.awt.Cursor.HAND_CURSOR)); + textarea.setAlignmentX(LEFT_ALIGNMENT); + urlarea.setAlignmentX(LEFT_ALIGNMENT); + vbox.add(textarea); + vbox.add(urlarea); + + permissionScroller.setAlignmentX(LEFT_ALIGNMENT); + vbox.add(permissionScroller); + vbox.add(Box.createVerticalStrut(GAP)); + + descriptionLabel = new JLabel(); + descriptionLabel.setPreferredSize(new Dimension(DESC_WIDTH, DESC_HEIGHT)); + descriptionLabel.setVerticalAlignment(SwingConstants.TOP); + descriptionLabel.setAlignmentX(LEFT_ALIGNMENT); + vbox.add(descriptionLabel); + vbox.add(Box.createVerticalStrut(GAP)); + + JPanel buttons = new JPanel(); + buttons.setAlignmentX(LEFT_ALIGNMENT); + JButton okButton = new JButton(Language.text("prompt.ok")); + Dimension dim = new Dimension(Toolkit.getButtonWidth(), + okButton.getPreferredSize().height); + okButton.setPreferredSize(dim); + okButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + saveSelections(); + setVisible(false); + } + }); + okButton.setEnabled(true); + + JButton cancelButton = new JButton(Language.text("prompt.cancel")); + cancelButton.setPreferredSize(dim); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + cancelButton.setEnabled(true); + + // think different, biznatchios! + if (Platform.isMacOS()) { + buttons.add(cancelButton); + buttons.add(okButton); + } else { + buttons.add(okButton); + buttons.add(cancelButton); + } + vbox.add(buttons); + + JRootPane root = getRootPane(); + root.setDefaultButton(okButton); + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + Toolkit.registerWindowCloseKeys(root, disposer); + Toolkit.setIcon(this); + + pack(); + + Dimension screen = Toolkit.getScreenSize(); + Dimension windowSize = getSize(); + + setLocation((screen.width - windowSize.width) / 2, + (screen.height - windowSize.height) / 2); + + Manifest mf = new Manifest(sketch, appComp, modeFolder, false); + setSelections(mf.getPermissions()); + + // show the window and get to work + setVisible(true); + } + + + @SuppressWarnings("rawtypes") + protected void setSelections(String[] sel) { +// processing.core.PApplet.println("permissions are:"); +// processing.core.PApplet.println(sel); + HashMap map = new HashMap(); + for (String s : sel) { + map.put(s, new Object()); + } + DefaultListModel model = (DefaultListModel) permissionList.getModel(); + for (int i = 0; i < count; i++) { + JCheckBox box = (JCheckBox) model.get(i); +// System.out.println(map.containsKey(box.getText()) + " " + box.getText()); + box.setSelected(map.containsKey(box.getText())); + } + } + + + @SuppressWarnings("rawtypes") + protected String[] getSelections() { + ArrayList sel = new ArrayList(); + DefaultListModel model = (DefaultListModel) permissionList.getModel(); + for (int i = 0; i < count; i++) { + if (((JCheckBox) model.get(i)).isSelected()) { + sel.add(title[i]); + } + } + return sel.toArray(new String[0]); + } + + + protected void saveSelections() { + String[] sel = getSelections(); + Manifest mf = new Manifest(sketch, appComp, modeFolder, false); + mf.setPermissions(sel); + } + + + public String getMenuTitle() { + return "Android Permissions"; + } + + + // List of constants for each permission and a brief description: + // https://developer.android.com/reference/android/Manifest.permission + static final String[] listing = { + "ACCEPT_HANDOVER", "Allows a calling app to continue a call which was started in another app.", + "ACCESS_BACKGROUND_LOCATION", "Allows an app to access location in the background.", + "ACCESS_BLOBS_ACROSS_USERS", "Allows an application to access data blobs across users.", + "ACCESS_CHECKIN_PROPERTIES", "Allows read/write access to the \"properties\" table in the checkin database, to change values that get uploaded.", + "ACCESS_COARSE_LOCATION", "Allows an app to access approximate location.", + "ACCESS_FINE_LOCATION", "Allows an app to access precise location.", + "ACCESS_HIDDEN_PROFILES", "Allows applications to access profiles with ACCESS_HIDDEN_PROFILES user property", + "ACCESS_LOCATION_EXTRA_COMMANDS", "Allows an application to access extra location provider commands.", + "ACCESS_MEDIA_LOCATION", "Allows an application to access any geographic locations persisted in the user's shared collection.", + "ACCESS_NETWORK_STATE", "Allows applications to access information about networks.", + "ACCESS_NOTIFICATION_POLICY", "Marker permission for applications that wish to access notification policy.", + "ACCESS_WIFI_STATE", "Allows applications to access information about Wi-Fi networks.", + "ACCOUNT_MANAGER", "Allows applications to call into AccountAuthenticators.", + "ACTIVITY_RECOGNITION", "Allows an application to recognize physical activity.", + "ADD_VOICEMAIL", "Allows an application to add voicemails into the system.", + "ANSWER_PHONE_CALLS", "Allows the app to answer an incoming phone call.", + "BATTERY_STATS", "Allows an application to collect battery statistics", + "BIND_ACCESSIBILITY_SERVICE", "Must be required by an AccessibilityService , to ensure that only the system can bind to it.", + "BIND_APPWIDGET", "Allows an application to tell the AppWidget service which application can access AppWidget's data.", + "BIND_AUTOFILL_SERVICE", "Must be required by a AutofillService , to ensure that only the system can bind to it.", + "BIND_CALL_REDIRECTION_SERVICE", "Must be required by a CallRedirectionService , to ensure that only the system can bind to it.", + "BIND_CARRIER_MESSAGING_CLIENT_SERVICE", "A subclass of CarrierMessagingClientService must be protected with this permission.", + "BIND_CARRIER_SERVICES", "The system process that is allowed to bind to services in carrier apps will have this permission.", + "BIND_COMPANION_DEVICE_SERVICE", "Must be required by any CompanionDeviceService s to ensure that only the system can bind to it.", + "BIND_CONDITION_PROVIDER_SERVICE", "Must be required by a ConditionProviderService , to ensure that only the system can bind to it.", + "BIND_CONTROLS", "Allows SystemUI to request third party controls.", + "BIND_CREDENTIAL_PROVIDER_SERVICE", "Must be required by a CredentialProviderService to ensure that only the system can bind to it.", + "BIND_DEVICE_ADMIN", "Must be required by device administration receiver, to ensure that only the system can interact with it.", + "BIND_DREAM_SERVICE", "Must be required by an DreamService , to ensure that only the system can bind to it.", + "BIND_INCALL_SERVICE", "Must be required by a InCallService , to ensure that only the system can bind to it.", + "BIND_INPUT_METHOD", "Must be required by an InputMethodService , to ensure that only the system can bind to it.", + "BIND_MIDI_DEVICE_SERVICE", "Must be required by an MidiDeviceService , to ensure that only the system can bind to it.", + "BIND_NFC_SERVICE", "Must be required by a HostApduService or OffHostApduService to ensure that only the system can bind to it.", + "BIND_NOTIFICATION_LISTENER_SERVICE", "Must be required by an NotificationListenerService , to ensure that only the system can bind to it.", + "BIND_PRINT_SERVICE", "Must be required by a PrintService , to ensure that only the system can bind to it.", + "BIND_QUICK_ACCESS_WALLET_SERVICE", "Must be required by a QuickAccessWalletService to ensure that only the system can bind to it.", + "BIND_QUICK_SETTINGS_TILE", "Allows an application to bind to third party quick settings tiles.", + "BIND_REMOTEVIEWS", "Must be required by a RemoteViewsService , to ensure that only the system can bind to it.", + "BIND_SCREENING_SERVICE", "Must be required by a CallScreeningService , to ensure that only the system can bind to it.", + "BIND_TELECOM_CONNECTION_SERVICE", "Must be required by a ConnectionService , to ensure that only the system can bind to it.", + "BIND_TEXT_SERVICE", "Must be required by a TextService (e.g. SpellCheckerService) to ensure that only the system can bind to it.", + "BIND_TV_INPUT", "Must be required by a TvInputService to ensure that only the system can bind to it.", + "BIND_TV_INTERACTIVE_APP", "Must be required by a TvInteractiveAppService to ensure that only the system can bind to it.", + "BIND_VISUAL_VOICEMAIL_SERVICE", "Must be required by a link VisualVoicemailService to ensure that only the system can bind to it.", + "BIND_VOICE_INTERACTION", "Must be required by a VoiceInteractionService , to ensure that only the system can bind to it.", + "BIND_VPN_SERVICE", "Must be required by a VpnService , to ensure that only the system can bind to it.", + "BIND_VR_LISTENER_SERVICE", "Must be required by an VrListenerService , to ensure that only the system can bind to it.", + "BIND_WALLPAPER", "Must be required by a WallpaperService , to ensure that only the system can bind to it.", + "BLUETOOTH", "Allows applications to connect to paired bluetooth devices.", + "BLUETOOTH_ADMIN", "Allows applications to discover and pair bluetooth devices.", + "BLUETOOTH_ADVERTISE", "Required to be able to advertise to nearby Bluetooth devices.", + "BLUETOOTH_CONNECT", "Required to be able to connect to paired Bluetooth devices.", + "BLUETOOTH_PRIVILEGED", "Allows applications to pair bluetooth devices without user interaction, and to allow or disallow phonebook access or message access.", + "BLUETOOTH_SCAN", "Required to be able to discover and pair nearby Bluetooth devices.", + "BODY_SENSORS", "Allows an application to access data from sensors that the user uses to measure what is happening inside their body, such as heart rate.", + "BODY_SENSORS_BACKGROUND", "Allows an application to access data from sensors that the user uses to measure what is happening inside their body, such as heart rate.", + "BROADCAST_PACKAGE_REMOVED", "Allows an application to broadcast a notification that an application package has been removed.", + "BROADCAST_SMS", "Allows an application to broadcast an SMS receipt notification.", + "BROADCAST_STICKY", "Allows an application to broadcast sticky intents.", + "BROADCAST_WAP_PUSH", "Allows an application to broadcast a WAP PUSH receipt notification.", + "CALL_COMPANION_APP", "Allows an app which implements the InCallService API to be eligible to be enabled as a calling companion app.", + "CALL_PHONE", "Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call.", + "CALL_PRIVILEGED", "Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user to confirm the call being placed.", + "CAMERA", "Required to be able to access the camera device.", + "CAPTURE_AUDIO_OUTPUT", "Allows an application to capture audio output.", + "CHANGE_COMPONENT_ENABLED_STATE", "Allows an application to change whether an application component (other than its own) is enabled or not.", + "CHANGE_CONFIGURATION", "Allows an application to modify the current configuration, such as locale.", + "CHANGE_NETWORK_STATE", "Allows applications to change network connectivity state.", + "CHANGE_WIFI_MULTICAST_STATE", "Allows applications to enter Wi-Fi Multicast mode.", + "CHANGE_WIFI_STATE", "Allows applications to change Wi-Fi connectivity state.", + "CLEAR_APP_CACHE", "Allows an application to clear the caches of all installed applications on the device.", + "CONFIGURE_WIFI_DISPLAY", "Allows an application to configure and connect to Wifi displays", + "CONTROL_LOCATION_UPDATES", "Allows enabling/disabling location update notifications from the radio.", + "CREDENTIAL_MANAGER_QUERY_CANDIDATE_CREDENTIALS", "Allows a browser to invoke the set of query apis to get metadata about credential candidates prepared during the CredentialManager.prepareGetCredential API.", + "CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS", "Allows specifying candidate credential providers to be queried in Credential Manager get flows, or to be preferred as a default in the Credential Manager create flows.", + "CREDENTIAL_MANAGER_SET_ORIGIN", "Allows a browser to invoke credential manager APIs on behalf of another RP.", + "DELETE_CACHE_FILES", "Old permission for deleting an app's cache files, no longer used, but signals for us to quietly ignore calls instead of throwing an exception.", + "DELETE_PACKAGES", "Allows an application to delete packages.", + "DELIVER_COMPANION_MESSAGES", "Allows an application to deliver companion messages to system", + "DETECT_SCREEN_CAPTURE", "Allows an application to get notified when a screen capture of its windows is attempted.", + "DETECT_SCREEN_RECORDING", "Allows an application to get notified when it is being recorded.", + "DIAGNOSTIC", "Allows applications to RW to diagnostic resources.", + "DISABLE_KEYGUARD", "Allows applications to disable the keyguard if it is not secure.", + "DUMP", "Allows an application to retrieve state dump information from system services.", + "ENFORCE_UPDATE_OWNERSHIP", "Allows an application to indicate via PackageInstaller.SessionParams.setRequestUpdateOwnership(boolean) that it has the intention of becoming the update owner.", + "EXECUTE_APP_ACTION", "Allows an assistive application to perform actions on behalf of users inside of applications.", + "EXPAND_STATUS_BAR", "Allows an application to expand or collapse the status bar.", + "FACTORY_TEST", "Run as a manufacturer test application, running as the root user.", + "FOREGROUND_SERVICE", "Allows a regular application to use Service.startForeground .", + "FOREGROUND_SERVICE_CAMERA", "Allows a regular application to use Service.startForeground with the type \"camera\".", + "FOREGROUND_SERVICE_CONNECTED_DEVICE", "Allows a regular application to use Service.startForeground with the type \"connectedDevice\".", + "FOREGROUND_SERVICE_DATA_SYNC", "Allows a regular application to use Service.startForeground with the type \"dataSync\".", + "FOREGROUND_SERVICE_HEALTH", "Allows a regular application to use Service.startForeground with the type \"health\".", + "FOREGROUND_SERVICE_LOCATION", "Allows a regular application to use Service.startForeground with the type \"location\".", + "FOREGROUND_SERVICE_MEDIA_PLAYBACK", "Allows a regular application to use Service.startForeground with the type \"mediaPlayback\".", + "FOREGROUND_SERVICE_MEDIA_PROCESSING", "Allows a regular application to use Service.startForeground with the type \"mediaProcessing\".", + "FOREGROUND_SERVICE_MEDIA_PROJECTION", "Allows a regular application to use Service.startForeground with the type \"mediaProjection\".", + "FOREGROUND_SERVICE_MICROPHONE", "Allows a regular application to use Service.startForeground with the type \"microphone\".", + "FOREGROUND_SERVICE_PHONE_CALL", "Allows a regular application to use Service.startForeground with the type \"phoneCall\".", + "FOREGROUND_SERVICE_REMOTE_MESSAGING", "Allows a regular application to use Service.startForeground with the type \"remoteMessaging\".", + "FOREGROUND_SERVICE_SPECIAL_USE", "Allows a regular application to use Service.startForeground with the type \"specialUse\".", + "FOREGROUND_SERVICE_SYSTEM_EXEMPTED", "Allows a regular application to use Service.startForeground with the type \"systemExempted\".", + "GET_ACCOUNTS", "Allows access to the list of accounts in the Accounts Service.", + "GET_ACCOUNTS_PRIVILEGED", "Allows access to the list of accounts in the Accounts Service.", + "GET_PACKAGE_SIZE", "Allows an application to find out the space used by any package.", + "GLOBAL_SEARCH", "This permission can be used on content providers to allow the global search system to access their data.", + "HIDE_OVERLAY_WINDOWS", "Allows an app to prevent non-system-overlay windows from being drawn on top of it", + "HIGH_SAMPLING_RATE_SENSORS", "Allows an app to access sensor data with a sampling rate greater than 200 Hz.", + "INSTALL_LOCATION_PROVIDER", "Allows an application to install a location provider into the Location Manager.", + "INSTALL_PACKAGES", "Allows an application to install packages.", + "INSTALL_SHORTCUT", "Allows an application to install a shortcut in Launcher.", + "INSTANT_APP_FOREGROUND_SERVICE", "Allows an instant app to create foreground services.", + "INTERACT_ACROSS_PROFILES", "Allows interaction across profiles in the same profile group.", + "INTERNET", "Allows applications to open network sockets.", + "KILL_BACKGROUND_PROCESSES", "Allows an application to call ActivityManager.killBackgroundProcesses(String) .", + "LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE", "Allows an application to capture screen content to perform a screenshot using the intent action Intent.ACTION_LAUNCH_CAPTURE_CONTENT_ACTIVITY_FOR_NOTE .", + "LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK", "An application needs this permission for Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY to show its Activity embedded in Settings app.", + "LOADER_USAGE_STATS", "Allows a data loader to read a package's access logs.", + "LOCATION_HARDWARE", "Allows an application to use location features in hardware, such as the geofencing api.", + "MANAGE_DEVICE_LOCK_STATE", "Allows financed device kiosk apps to perform actions on the Device Lock service", + "MANAGE_DEVICE_POLICY_ACCESSIBILITY", "Allows an application to manage policy related to accessibility.", + "MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT", "Allows an application to set policy related to account management.", + "MANAGE_DEVICE_POLICY_ACROSS_USERS", "Allows an application to set device policies outside the current user that are required for securing device ownership without accessing user data.", + "MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL", "Allows an application to set device policies outside the current user.", + "MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL", "Allows an application to set device policies outside the current user that are critical for securing data within the current user.", + "MANAGE_DEVICE_POLICY_AIRPLANE_MODE", "Allows an application to set policy related to airplane mode.", + "MANAGE_DEVICE_POLICY_APPS_CONTROL", "Allows an application to manage policy regarding modifying applications.", + "MANAGE_DEVICE_POLICY_APP_RESTRICTIONS", "Allows an application to manage application restrictions.", + "MANAGE_DEVICE_POLICY_APP_USER_DATA", "Allows an application to manage policy related to application user data.", + "MANAGE_DEVICE_POLICY_ASSIST_CONTENT", "Allows an application to set policy related to sending assist content to a privileged app such as the Assistant app.", + "MANAGE_DEVICE_POLICY_AUDIO_OUTPUT", "Allows an application to set policy related to audio output.", + "MANAGE_DEVICE_POLICY_AUTOFILL", "Allows an application to set policy related to autofill.", + "MANAGE_DEVICE_POLICY_BACKUP_SERVICE", "Allows an application to manage backup service policy.", + "MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL", "Allows an application to manage policy related to block package uninstallation.", + "MANAGE_DEVICE_POLICY_BLUETOOTH", "Allows an application to set policy related to bluetooth.", + "MANAGE_DEVICE_POLICY_BUGREPORT", "Allows an application to request bugreports with user consent.", + "MANAGE_DEVICE_POLICY_CALLS", "Allows an application to manage calling policy.", + "MANAGE_DEVICE_POLICY_CAMERA", "Allows an application to set policy related to restricting a user's ability to use or enable and disable the camera.", + "MANAGE_DEVICE_POLICY_CAMERA_TOGGLE", "Allows an application to manage policy related to camera toggle.", + "MANAGE_DEVICE_POLICY_CERTIFICATES", "Allows an application to set policy related to certificates.", + "MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE", "Allows an application to manage policy related to common criteria mode.", + "MANAGE_DEVICE_POLICY_CONTENT_PROTECTION", "Allows an application to manage policy related to content protection.", + "MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES", "Allows an application to manage debugging features policy.", + "MANAGE_DEVICE_POLICY_DEFAULT_SMS", "Allows an application to set policy related to the default sms application.", + "MANAGE_DEVICE_POLICY_DEVICE_IDENTIFIERS", "Allows an application to manage policy related to device identifiers.", + "MANAGE_DEVICE_POLICY_DISPLAY", "Allows an application to set policy related to the display.", + "MANAGE_DEVICE_POLICY_FACTORY_RESET", "Allows an application to set policy related to factory reset.", + "MANAGE_DEVICE_POLICY_FUN", "Allows an application to set policy related to fun.", + "MANAGE_DEVICE_POLICY_INPUT_METHODS", "Allows an application to set policy related to input methods.", + "MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES", "Allows an application to manage installing from unknown sources policy.", + "MANAGE_DEVICE_POLICY_KEEP_UNINSTALLED_PACKAGES", "Allows an application to set policy related to keeping uninstalled packages.", + "MANAGE_DEVICE_POLICY_KEYGUARD", "Allows an application to manage policy related to keyguard.", + "MANAGE_DEVICE_POLICY_LOCALE", "Allows an application to set policy related to locale.", + "MANAGE_DEVICE_POLICY_LOCATION", "Allows an application to set policy related to location.", + "MANAGE_DEVICE_POLICY_LOCK", "Allows an application to lock a profile or the device with the appropriate cross-user permission.", + "MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS", "Allows an application to set policy related to lock credentials.", + "MANAGE_DEVICE_POLICY_LOCK_TASK", "Allows an application to manage lock task policy.", + "MANAGE_DEVICE_POLICY_MANAGED_SUBSCRIPTIONS", "Allows an application to set policy related to subscriptions downloaded by an admin.", + "MANAGE_DEVICE_POLICY_METERED_DATA", "Allows an application to manage policy related to metered data.", + "MANAGE_DEVICE_POLICY_MICROPHONE", "Allows an application to set policy related to restricting a user's ability to use or enable and disable the microphone.", + "MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE", "Allows an application to manage policy related to microphone toggle.", + "MANAGE_DEVICE_POLICY_MOBILE_NETWORK", "Allows an application to set policy related to mobile networks.", + "MANAGE_DEVICE_POLICY_MODIFY_USERS", "Allows an application to manage policy preventing users from modifying users.", + "MANAGE_DEVICE_POLICY_MTE", "Allows an application to manage policy related to the Memory Tagging Extension (MTE).", + "MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION", "Allows an application to set policy related to nearby communications (e.g. Beam and nearby streaming).", + "MANAGE_DEVICE_POLICY_NETWORK_LOGGING", "Allows an application to set policy related to network logging.", + "MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY", "Allows an application to manage the identity of the managing organization.", + "MANAGE_DEVICE_POLICY_OVERRIDE_APN", "Allows an application to set policy related to override APNs.", + "MANAGE_DEVICE_POLICY_PACKAGE_STATE", "Allows an application to set policy related to hiding and suspending packages.", + "MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA", "Allows an application to set policy related to physical media.", + "MANAGE_DEVICE_POLICY_PRINTING", "Allows an application to set policy related to printing.", + "MANAGE_DEVICE_POLICY_PRIVATE_DNS", "Allows an application to set policy related to private DNS.", + "MANAGE_DEVICE_POLICY_PROFILES", "Allows an application to set policy related to profiles.", + "MANAGE_DEVICE_POLICY_PROFILE_INTERACTION", "Allows an application to set policy related to interacting with profiles (e.g. Disallowing cross-profile copy and paste).", + "MANAGE_DEVICE_POLICY_PROXY", "Allows an application to set a network-independent global HTTP proxy.", + "MANAGE_DEVICE_POLICY_QUERY_SYSTEM_UPDATES", "Allows an application query system updates.", + "MANAGE_DEVICE_POLICY_RESET_PASSWORD", "Allows an application to force set a new device unlock password or a managed profile challenge on current user.", + "MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS", "Allows an application to set policy related to restricting the user from configuring private DNS.", + "MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS", "Allows an application to set the grant state of runtime permissions on packages.", + "MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND", "Allows an application to set policy related to users running in the background.", + "MANAGE_DEVICE_POLICY_SAFE_BOOT", "Allows an application to manage safe boot policy.", + "MANAGE_DEVICE_POLICY_SCREEN_CAPTURE", "Allows an application to set policy related to screen capture.", + "MANAGE_DEVICE_POLICY_SCREEN_CONTENT", "Allows an application to set policy related to the usage of the contents of the screen.", + "MANAGE_DEVICE_POLICY_SECURITY_LOGGING", "Allows an application to set policy related to security logging.", + "MANAGE_DEVICE_POLICY_SETTINGS", "Allows an application to set policy related to settings.", + "MANAGE_DEVICE_POLICY_SMS", "Allows an application to set policy related to sms.", + "MANAGE_DEVICE_POLICY_STATUS_BAR", "Allows an application to set policy related to the status bar.", + "MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE", "Allows an application to set support messages for when a user action is affected by an active policy.", + "MANAGE_DEVICE_POLICY_SUSPEND_PERSONAL_APPS", "Allows an application to set policy related to suspending personal apps.", + "MANAGE_DEVICE_POLICY_SYSTEM_APPS", "Allows an application to manage policy related to system apps.", + "MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS", "Allows an application to set policy related to system dialogs.", + "MANAGE_DEVICE_POLICY_SYSTEM_UPDATES", "Allows an application to set policy related to system updates.", + "MANAGE_DEVICE_POLICY_TIME", "Allows an application to manage device policy relating to time.", + "MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING", "Allows an application to set policy related to usb data signalling.", + "MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER", "Allows an application to set policy related to usb file transfers.", + "MANAGE_DEVICE_POLICY_USERS", "Allows an application to set policy related to users.", + "MANAGE_DEVICE_POLICY_VPN", "Allows an application to set policy related to VPNs.", + "MANAGE_DEVICE_POLICY_WALLPAPER", "Allows an application to set policy related to the wallpaper.", + "MANAGE_DEVICE_POLICY_WIFI", "Allows an application to set policy related to Wifi.", + "MANAGE_DEVICE_POLICY_WINDOWS", "Allows an application to set policy related to windows.", + "MANAGE_DEVICE_POLICY_WIPE_DATA", "Allows an application to manage policy related to wiping data.", + "MANAGE_DOCUMENTS", "Allows an application to manage access to documents, usually as part of a document picker.", + "MANAGE_EXTERNAL_STORAGE", "Allows an application a broad access to external storage in scoped storage.", + "MANAGE_MEDIA", "Allows an application to modify and delete media files on this device or any connected storage device without user confirmation.", + "MANAGE_ONGOING_CALLS", "Allows to query ongoing call details and manage ongoing calls", + "MANAGE_OWN_CALLS", "Allows a calling application which manages its own calls through the self-managed ConnectionService APIs.", + "MANAGE_WIFI_INTERFACES", "Allows applications to get notified when a Wi-Fi interface request cannot be satisfied without tearing down one or more other interfaces, and provide a decision whether to approve the request or reject it.", + "MANAGE_WIFI_NETWORK_SELECTION", "This permission is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs to improve wifi performance.", + "MASTER_CLEAR", "Not for use by third-party applications.", + "MEDIA_CONTENT_CONTROL", "Allows an application to know what content is playing and control its playback.", + "MEDIA_ROUTING_CONTROL", "Allows an application to control the routing of media apps.", + "MODIFY_AUDIO_SETTINGS", "Allows an application to modify global audio settings.", + "MODIFY_PHONE_STATE", "Allows modification of the telephony state - power on, mmi, etc.", + "MOUNT_FORMAT_FILESYSTEMS", "Allows formatting file systems for removable storage.", + "MOUNT_UNMOUNT_FILESYSTEMS", "Allows mounting and unmounting file systems for removable storage.", + "NEARBY_WIFI_DEVICES", "Required to be able to advertise and connect to nearby devices via Wi-Fi.", + "NFC", "Allows applications to perform I/O operations over NFC.", + "NFC_PREFERRED_PAYMENT_INFO", "Allows applications to receive NFC preferred payment service information.", + "NFC_TRANSACTION_EVENT", "Allows applications to receive NFC transaction events.", + "OVERRIDE_WIFI_CONFIG", "Allows an application to modify any wifi configuration, even if created by another application.", + "PACKAGE_USAGE_STATS", "Allows an application to collect component usage statistics", + "POST_NOTIFICATIONS", "Allows an app to post notifications", + "PROVIDE_OWN_AUTOFILL_SUGGESTIONS", "Allows an application to display its suggestions using the autofill framework.", + "PROVIDE_REMOTE_CREDENTIALS", "Allows an application to be able to store and retrieve credentials from a remote device.", + "QUERY_ALL_PACKAGES", "Allows query of any normal app on the device, regardless of manifest declarations.", + "READ_ASSISTANT_APP_SEARCH_DATA", "Allows an application to query over global data in AppSearch that's visible to the ASSISTANT role.", + "READ_BASIC_PHONE_STATE", "Allows read only access to phone state with a non dangerous permission, including the information like cellular network type, software version.", + "READ_CALENDAR", "Allows an application to read the user's calendar data.", + "READ_CALL_LOG", "Allows an application to read the user's call log.", + "READ_CONTACTS", "Allows an application to read the user's contacts data.", + "READ_DROPBOX_DATA", "Allows an application to access the data in Dropbox.", + "READ_EXTERNAL_STORAGE", "Allows an application to read from external storage.", + "READ_HOME_APP_SEARCH_DATA", "Allows an application to query over global data in AppSearch that's visible to the HOME role.", + "READ_LOGS", "Allows an application to read the low-level system log files.", + "READ_MEDIA_AUDIO", "Allows an application to read audio files from external storage.", + "READ_MEDIA_IMAGES", "Allows an application to read image files from external storage.", + "READ_MEDIA_VIDEO", "Allows an application to read video files from external storage.", + "READ_MEDIA_VISUAL_USER_SELECTED", "Allows an application to read image or video files from external storage that a user has selected via the permission prompt photo picker.", + "READ_NEARBY_STREAMING_POLICY", "Allows an application to read nearby streaming policy.", + "READ_PHONE_NUMBERS", "Allows read access to the device's phone number(s).", + "READ_PHONE_STATE", "Allows read only access to phone state, including the current cellular network information, the status of any ongoing calls, and a list of any PhoneAccount s registered on the device.", + "READ_PRECISE_PHONE_STATE", "Allows read only access to precise phone state.", + "READ_SMS", "Allows an application to read SMS messages.", + "READ_SYNC_SETTINGS", "Allows applications to read the sync settings.", + "READ_SYNC_STATS", "Allows applications to read the sync stats.", + "READ_VOICEMAIL", "Allows an application to read voicemails in the system.", + "REBOOT", "Required to be able to reboot the device.", + "RECEIVE_BOOT_COMPLETED", "Allows an application to receive the Intent.ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting.", + "RECEIVE_MMS", "Allows an application to monitor incoming MMS messages.", + "RECEIVE_SMS", "Allows an application to receive SMS messages.", + "RECEIVE_WAP_PUSH", "Allows an application to receive WAP push messages.", + "RECORD_AUDIO", "Allows an application to record audio.", + "REORDER_TASKS", "Allows an application to change the Z-order of tasks.", + "REQUEST_COMPANION_PROFILE_APP_STREAMING", "Allows application to request to be associated with a virtual display capable of streaming Android applications ( AssociationRequest.DEVICE_PROFILE_APP_STREAMING ) by CompanionDeviceManager .", + "REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION", "Allows application to request to be associated with a vehicle head unit capable of automotive projection ( AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ) by CompanionDeviceManager .", + "REQUEST_COMPANION_PROFILE_COMPUTER", "Allows application to request to be associated with a computer to share functionality and/or data with other devices, such as notifications, photos and media ( AssociationRequest.DEVICE_PROFILE_COMPUTER ) by CompanionDeviceManager .", + "REQUEST_COMPANION_PROFILE_GLASSES", "Allows app to request to be associated with a device via CompanionDeviceManager as \"glasses\"", + "REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING", "Allows application to request to stream content from an Android host to a nearby device ( AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING ) by CompanionDeviceManager .", + "REQUEST_COMPANION_PROFILE_WATCH", "Allows app to request to be associated with a device via CompanionDeviceManager as a \"watch\"", + "REQUEST_COMPANION_RUN_IN_BACKGROUND", "Allows a companion app to run in the background.", + "REQUEST_COMPANION_SELF_MANAGED", "Allows an application to create a \"self-managed\" association.", + "REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND", "Allows a companion app to start a foreground service from the background.", + "REQUEST_COMPANION_USE_DATA_IN_BACKGROUND", "Allows a companion app to use data in the background.", + "REQUEST_DELETE_PACKAGES", "Allows an application to request deleting packages.", + "REQUEST_IGNORE_BATTERY_OPTIMIZATIONS", "Permission an application must hold in order to use Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS .", + "REQUEST_INSTALL_PACKAGES", "Allows an application to request installing packages.", + "REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE", "Allows an application to subscribe to notifications about the presence status change of their associated companion device", + "REQUEST_PASSWORD_COMPLEXITY", "Allows an application to request the screen lock complexity and prompt users to update the screen lock to a certain complexity level.", + "RUN_USER_INITIATED_JOBS", "Allows applications to use the user-initiated jobs API.", + "SCHEDULE_EXACT_ALARM", "Allows applications to use exact alarm APIs.", + "SEND_RESPOND_VIA_MESSAGE", "Allows an application (Phone) to send a request to other applications to handle the respond-via-message action during incoming calls.", + "SEND_SMS", "Allows an application to send SMS messages.", + "SET_ALARM", "Allows an application to broadcast an Intent to set an alarm for the user.", + "SET_ALWAYS_FINISH", "Allows an application to control whether activities are immediately finished when put in the background.", + "SET_ANIMATION_SCALE", "Modify the global animation scaling factor.", + "SET_BIOMETRIC_DIALOG_ADVANCED", "Allows an application to set the advanced features on BiometricDialog (SystemUI), including logo, logo description, and content view with more options button.", + "SET_DEBUG_APP", "Configure an application for debugging.", + "SET_PROCESS_LIMIT", "Allows an application to set the maximum number of (not needed) application processes that can be running.", + "SET_TIME", "Allows applications to set the system time directly.", + "SET_TIME_ZONE", "Allows applications to set the system time zone directly.", + "SET_WALLPAPER", "Allows applications to set the wallpaper.", + "SET_WALLPAPER_HINTS", "Allows applications to set the wallpaper hints.", + "SIGNAL_PERSISTENT_PROCESSES", "Allow an application to request that a signal be sent to all persistent processes.", + "START_FOREGROUND_SERVICES_FROM_BACKGROUND", "Allows an application to start foreground services from the background at any time.", + "START_VIEW_APP_FEATURES", "Allows the holder to start the screen with a list of app features.", + "START_VIEW_PERMISSION_USAGE", "Allows the holder to start the permission usage screen for an app.", + "STATUS_BAR", "Allows an application to open, close, or disable the status bar and its icons.", + "SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE", "Allows an application to subscribe to keyguard locked (i.e., showing) state.", + "SYSTEM_ALERT_WINDOW", "Allows an app to create windows using the type WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY , shown on top of all other apps.", + "TRANSMIT_IR", "Allows using the device's IR transmitter, if available.", + "TURN_SCREEN_ON", "Allows an app to turn on the screen on, e.g. with PowerManager.ACQUIRE_CAUSES_WAKEUP .", + "UPDATE_DEVICE_STATS", "Allows an application to update device statistics.", + "UPDATE_PACKAGES_WITHOUT_USER_ACTION", "Allows an application to indicate via PackageInstaller.SessionParams.setRequireUserAction(int) that user action should not be required for an app update.", + "USE_BIOMETRIC", "Allows an app to use device supported biometric modalities.", + "USE_EXACT_ALARM", "Allows apps to use exact alarms just like with SCHEDULE_EXACT_ALARM but without needing to request this permission from the user.", + "USE_FULL_SCREEN_INTENT", "Required for apps targeting Build.VERSION_CODES.Q that want to use notification full screen intents .", + "USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER", "Allows to read device identifiers and use ICC based authentication like EAP-AKA.", + "USE_SIP", "Allows an application to use SIP service.", + "UWB_RANGING", "Required to be able to range to devices using ultra-wideband.", + "VIBRATE", "Allows access to the vibrator.", + "WAKE_LOCK", "Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming.", + "WRITE_APN_SETTINGS", "Allows applications to write the apn settings and read sensitive fields of an existing apn settings like user and password.", + "WRITE_CALENDAR", "Allows an application to write the user's calendar data.", + "WRITE_CALL_LOG", "Allows an application to write and read the user's call log data.", + "WRITE_CONTACTS", "Allows an application to write the user's contacts data.", + "WRITE_EXTERNAL_STORAGE", "Allows an application to write to external storage.", + "WRITE_GSERVICES", "Allows an application to modify the Google service map.", + "WRITE_SECURE_SETTINGS", "Allows an application to read or write the secure system settings.", + "WRITE_SETTINGS", "Allows an application to read or write the system settings.", + "WRITE_SYNC_SETTINGS", "Allows applications to write the sync settings.", + "WRITE_VOICEMAIL", "Allows an application to modify and remove existing voicemails in the system." + }; + + // Dangerous permissions that need runtime approval: + // https://developer.android.com/guide/topics/permissions/overview#runtime + public static final String[] dangerous = { + "ACCEPT_HANDOVER", + "ACCESS_BACKGROUND_LOCATION", + "ACCESS_COARSE_LOCATION", + "ACCESS_FINE_LOCATION", + "ACCESS_MEDIA_LOCATION", + "ACTIVITY_RECOGNITION", + "ADD_VOICEMAIL", + "ANSWER_PHONE_CALLS", + "BLUETOOTH_ADVERTISE", + "BLUETOOTH_CONNECT", + "BLUETOOTH_SCAN", + "BODY_SENSORS", + "BODY_SENSORS_BACKGROUND", + "CALL_PHONE", + "CAMERA", + "GET_ACCOUNTS", + "NEARBY_WIFI_DEVICES", + "POST_NOTIFICATIONS", + "READ_CALENDAR", + "READ_CALL_LOG", + "READ_CONTACTS", + "READ_EXTERNAL_STORAGE", + "READ_MEDIA_AUDIO", + "READ_MEDIA_IMAGES", + "READ_MEDIA_VIDEO", + "READ_MEDIA_VISUAL_USER_SELECTED", + "READ_PHONE_NUMBERS", + "READ_PHONE_STATE", + "READ_SMS", + "RECEIVE_MMS", + "RECEIVE_SMS", + "RECEIVE_WAP_PUSH", + "RECORD_AUDIO", + "SEND_SMS", + "USE_SIP", + "UWB_RANGING", + "WRITE_CALENDAR", + "WRITE_CALL_LOG", + "WRITE_CONTACTS", + "WRITE_EXTERNAL_STORAGE" + }; + + static String[] title; + static String[] description; + static int count; + static { + count = listing.length / 2; + title = new String[count]; + description = new String[count]; + for (int i = 0; i < count; i++) { + title[i] = listing[i*2]; + description[i] = listing[i*2+1]; + } + } +} + + +// Code for this CheckBoxList class found on the net, though I've lost the +// link. If you run across the original version, please let me know so that +// the original author can be credited properly. It was from a snippet +// collection, but it seems to have been picked up so many places with others +// placing their copyright on it that I haven't been able to determine the +// original author. [fry 20100216] +@SuppressWarnings("serial") +class CheckBoxList extends JList { + protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); + int checkboxWidth; + + public CheckBoxList() { + setCellRenderer(new CellRenderer()); + + // get the width of a checkbox so we can figure out if the mouse is inside + checkboxWidth = new JCheckBox().getPreferredSize().width; + // add the amount for the inset + checkboxWidth += Permissions.BORDER_HORIZ; + + addMouseListener(new MouseAdapter() { + public void mousePressed(MouseEvent e) { + if (isEnabled()) { +// System.out.println("cbw = " + checkboxWidth); + int index = locationToIndex(e.getPoint()); +// descriptionLabel.setText(description[index]); + if (index != -1) { + JCheckBox checkbox = getModel().getElementAt(index); + //System.out.println("mouse event in list: " + e); +// System.out.println(checkbox.getSize() + " ... " + checkbox); +// if (e.getX() < checkbox.getSize().height) { + if (e.getX() < checkboxWidth) { + checkbox.setSelected(!checkbox.isSelected()); + repaint(); + } + } + } + } + }); + setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + } + + + protected class CellRenderer implements ListCellRenderer { + public Component getListCellRendererComponent(JList list, + JCheckBox checkbox, + int index, boolean isSelected, + boolean cellHasFocus) { + checkbox.setBackground(isSelected ? getSelectionBackground() : getBackground()); + checkbox.setForeground(isSelected ? getSelectionForeground() : getForeground()); + checkbox.setEnabled(list.isEnabled()); + checkbox.setFont(getFont()); + checkbox.setFocusPainted(false); + checkbox.setBorderPainted(true); + checkbox.setBorder(isSelected ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder); + return checkbox; + } + } +} diff --git a/processing/mode/src/processing/mode/android/RedirectStreamHandler.java b/processing/mode/src/processing/mode/android/RedirectStreamHandler.java new file mode 100644 index 000000000..933669106 --- /dev/null +++ b/processing/mode/src/processing/mode/android/RedirectStreamHandler.java @@ -0,0 +1,34 @@ +package processing.mode.android; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; + + +public class RedirectStreamHandler extends Thread { + // Streams Redirection- from and to + private final InputStream input; + private final PrintWriter output; + + RedirectStreamHandler(PrintWriter output, InputStream input) { + this.input = input; + this.output = output; + start(); + } + + @Override + public void run() { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + String line; + while ((line = reader.readLine()) != null) { + // print to output line by line + output.println(line); + } + } catch (IOException ioException) { + System.out.println("I/O Redirection failure: "+ ioException.toString()); + } + } + } diff --git a/processing/mode/src/processing/mode/android/SDKDownloader.java b/processing/mode/src/processing/mode/android/SDKDownloader.java new file mode 100644 index 000000000..2f9312b81 --- /dev/null +++ b/processing/mode/src/processing/mode/android/SDKDownloader.java @@ -0,0 +1,690 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2014-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.mode.android; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.ui.Toolkit; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +@SuppressWarnings("serial") +public class SDKDownloader extends JDialog implements PropertyChangeListener { + final static private int BOX_BORDER = Toolkit.zoom(13); + final static private int BAR_BORDER = Toolkit.zoom(10); + final static private int BAR_WIDTH = Toolkit.zoom(300); + final static private int BAR_HEIGHT = Toolkit.zoom(30); + final static private int GAP = Toolkit.zoom(13); + + private static final int PLATFORM_TOOLS = 2; + private static final int ANDROID_REPO = 4; + private static final int GOOGLE_REPO = 5; + private static final int USB_DRIVER = 6; + + private static final String REPOSITORY_URL = "https://dl.google.com/android/repository/"; + private static final String HAXM_URL = "https://dl.google.com/android/repository/extras/intel/"; + private static final String REPOSITORY_LIST = "repository2-3.xml"; + private static final String ADDON_LIST = "addon2-3.xml"; + + public static final boolean DOWNLOAD_EMU_WITH_SDK = false; + + private JProgressBar progressBar; + private JLabel downloadedTextArea; + + private SDKDownloadTask downloadTask; + + private Frame editor; + private AndroidSDK sdk; + private boolean cancelled; + + private int totalSize = 0; + + class SDKUrlHolder { + public String platformVersion, buildToolsVersion; + public String platformToolsUrl, buildToolsUrl, platformUrl, cmdlineToolsUrl, emulatorUrl; + public String platformToolsFilename, buildToolsFilename, platformFilename, cmdlineToolsFilename, emulatorFilename; + public String usbDriverUrl; + public String usbDriverFilename; + public String haxmFilename, haxmUrl; + public int totalSize = 0; + } + + class SDKDownloadTask extends SwingWorker { + + private int downloadedSize = 0; + private int BUFFER_SIZE = 4096; + + @Override + protected Object doInBackground() throws Exception { + File sketchbookFolder = processing.app.Base.getSketchbookFolder(); + File androidFolder = new File(sketchbookFolder, "android"); + if (!androidFolder.exists()) androidFolder.mkdir(); + + File sdkFolder = AndroidUtil.createSubFolder(androidFolder, "sdk"); + + // creating sdk folders + File platformsFolder = new File(sdkFolder, "platforms"); + if (!platformsFolder.exists()) platformsFolder.mkdir(); + File buildToolsFolder = new File(sdkFolder, "build-tools"); + if (!buildToolsFolder.exists()) buildToolsFolder.mkdir(); + File extrasFolder = new File(sdkFolder, "extras"); + if (!extrasFolder.exists()) extrasFolder.mkdir(); + File googleRepoFolder = new File(extrasFolder, "google"); + if (!googleRepoFolder.exists()) googleRepoFolder.mkdir(); + File haxmFolder = new File(extrasFolder, "intel/HAXM"); + if (!haxmFolder.exists()) haxmFolder.mkdirs(); + + if (DOWNLOAD_EMU_WITH_SDK) { + File emulatorFolder = new File(sdkFolder, "emulator"); + if (!emulatorFolder.exists()) emulatorFolder.mkdir(); + } + + // creating temp folder for downloaded zip packages + File tempFolder = new File(androidFolder, "temp"); + if (!tempFolder.exists()) tempFolder.mkdir(); + + try { + SDKUrlHolder downloadUrls = new SDKUrlHolder(); + String repositoryUrl = REPOSITORY_URL + REPOSITORY_LIST; + String addonUrl = REPOSITORY_URL + ADDON_LIST; + String haxmUrl = HAXM_URL + ADDON_LIST; + + String platformName = Platform.getName(); + System.out.println("PLATFORM NAME " + platformName); + if (platformName.equals("macos")) { + platformName = "macosx"; + } + getMainDownloadUrls(downloadUrls, repositoryUrl, platformName); + getExtrasDownloadUrls(downloadUrls, addonUrl, platformName); + getHaxmDownloadUrl(downloadUrls, haxmUrl, platformName); + firePropertyChange(AndroidMode.getTextString("download_property.change_event_total"), 0, downloadUrls.totalSize); + + // Command-line tools + File downloadedCmdLineTools = new File(tempFolder, downloadUrls.cmdlineToolsFilename); + downloadAndUnpack(downloadUrls.cmdlineToolsUrl, downloadedCmdLineTools, sdkFolder); + File tmpFrom = new File(sdkFolder, "cmdline-tools"); + File tmpTo = new File(sdkFolder, "cmdline-tmp"); + AndroidUtil.moveDir(tmpFrom, tmpTo); + File cmdlineToolsFolder = new File(sdkFolder, "cmdline-tools/latest"); + if (!cmdlineToolsFolder.exists()) cmdlineToolsFolder.mkdirs(); + AndroidUtil.moveDir(tmpTo, cmdlineToolsFolder); + + // Platform tools + File downloadedPlatformTools = new File(tempFolder, downloadUrls.platformToolsFilename); + downloadAndUnpack(downloadUrls.platformToolsUrl, downloadedPlatformTools, sdkFolder); + + // Build tools + File downloadedBuildTools = new File(tempFolder, downloadUrls.buildToolsFilename); + downloadAndUnpack(downloadUrls.buildToolsUrl, downloadedBuildTools, buildToolsFolder); + + // Platform + File downloadedPlatform = new File(tempFolder, downloadUrls.platformFilename); + downloadAndUnpack(downloadUrls.platformUrl, downloadedPlatform, platformsFolder); + + // USB driver + if (Platform.isWindows() && downloadUrls.usbDriverFilename != null) { + File downloadedFolder = new File(tempFolder, downloadUrls.usbDriverFilename); + downloadAndUnpack(downloadUrls.usbDriverUrl, downloadedFolder, googleRepoFolder); + } + + // HAXM + if (!Platform.isLinux() && downloadUrls.haxmFilename != null) { + File downloadedFolder = new File(tempFolder, downloadUrls.haxmFilename); + downloadAndUnpack(downloadUrls.haxmUrl, downloadedFolder, haxmFolder); + } + + if (DOWNLOAD_EMU_WITH_SDK && downloadUrls.emulatorFilename != null) { + // Emulator, unpacks directly to sdk folder + File downloadedEmulator = new File(tempFolder, downloadUrls.emulatorFilename); + downloadAndUnpack(downloadUrls.emulatorUrl, downloadedEmulator, sdkFolder); + } + + if (Platform.isLinux() || Platform.isMacOS()) { + Runtime.getRuntime().exec("chmod -R 755 " + sdkFolder.getAbsolutePath()); + } + + for (File f: tempFolder.listFiles()) f.delete(); + tempFolder.delete(); + + +// String actualName = platformsFolder.listFiles()[0].getName(); +// renameFolder(platformsFolder, "android-" + AndroidBuild.TARGET_SDK, actualName); + // Rename build-tools folder to the expected name if it's not that already + String actualName = buildToolsFolder.listFiles()[0].getName(); + renameFolder(buildToolsFolder, downloadUrls.buildToolsVersion, actualName); + + // Done, let's set the environment and load the new SDK! + Platform.setenv("ANDROID_SDK", sdkFolder.getAbsolutePath()); + Preferences.set("android.sdk.path", sdkFolder.getAbsolutePath()); + sdk = AndroidSDK.load(false, null); + } catch (ParserConfigurationException e) { + // TODO Handle exceptions here somehow (ie show error message) + // and handle at least mkdir() results (above) + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void done() { + super.done(); + setVisible(false); + dispose(); + } + + private void downloadAndUnpack(String urlString, File saveTo, + File unpackTo) throws IOException { + URL url = null; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + //This is expected for API level 14 and more + try { + url = new URL(REPOSITORY_URL + urlString); + } catch (MalformedURLException e1) { + //This exception is not expected. Need to return. + e1.printStackTrace(); + return; + } + } + URLConnection conn = url.openConnection(); + + InputStream inputStream = conn.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(saveTo); + + byte[] b = new byte[BUFFER_SIZE]; + int count; + while ((count = inputStream.read(b)) >= 0) { + outputStream.write(b, 0, count); + downloadedSize += count; + + firePropertyChange(AndroidMode.getTextString("download_property.change_event_downloaded"), 0, downloadedSize); + } + outputStream.flush(); outputStream.close(); inputStream.close(); + + inputStream.close(); + outputStream.close(); + + AndroidUtil.extractFolder(saveTo, unpackTo); + } + + private void getMainDownloadUrls(SDKUrlHolder urlHolder, + String repositoryUrl, String requiredHostOs) + throws ParserConfigurationException, IOException, SAXException, XPathException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new URL(repositoryUrl).openStream()); + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr; + NodeList remotePackages; + boolean found; + + // ----------------------------------------------------------------------- + // Platform + expr = xpath.compile("//remotePackage[starts-with(@path, \"platforms;\")" + + "and contains(@path, '" + AndroidBuild.TARGET_SDK + "')]"); // Skip latest platform; download only the targeted + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (remotePackages != null) { + NodeList childNodes = remotePackages.item(0).getChildNodes(); + + NodeList typeDetails = ((Element) childNodes).getElementsByTagName("type-details"); + NodeList apiLevel = ((Element) typeDetails.item(0)).getElementsByTagName("api-level"); + urlHolder.platformVersion = apiLevel.item(0).getTextContent(); + + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + NodeList archive = archives.item(0).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + urlHolder.platformFilename = url.item(0).getTextContent(); + urlHolder.platformUrl = REPOSITORY_URL + urlHolder.platformFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + } else { + throw new IOException(AndroidMode.getTextString("sdk_downloader.error_cannot_find_platform_files")); + } + + // Difference between platform tools, build tools, and SDK (now command-line) tools: + // http://stackoverflow.com/questions/19911762/what-is-android-sdk-build-tools-and-which-version-should-be-used + // Always get the latest! + + // ----------------------------------------------------------------------- + // Platform tools + expr = xpath.compile("//remotePackage[@path=\"platform-tools\"]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (remotePackages != null) { + parseAndSet(urlHolder, remotePackages, requiredHostOs, PLATFORM_TOOLS); + } else { + throw new IOException(AndroidMode.getTextString("sdk_downloader.error_cannot_find_platform_tools")); + } + + // ----------------------------------------------------------------------- + // Build tools + expr = xpath.compile("//remotePackage[starts-with(@path, \"build-tools;\")]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + found = false; + if (remotePackages != null) { + for (int buildTool = 0; buildTool < remotePackages.getLength(); buildTool++) { + NodeList childNodes = remotePackages.item(buildTool).getChildNodes(); + + NodeList channel = ((Element) childNodes).getElementsByTagName("channelRef"); + if (!channel.item(0).getAttributes().item(0).getNodeValue().equals("channel-0")) { + continue; // Stable channel only, skip others + } + + NodeList revision = ((Element) childNodes).getElementsByTagName("revision"); + String major = (((Element) revision.item(0)).getElementsByTagName("major")).item(0).getTextContent(); + String minor = (((Element) revision.item(0)).getElementsByTagName("minor")).item(0).getTextContent(); + String micro = (((Element) revision.item(0)).getElementsByTagName("micro")).item(0).getTextContent(); + if (!major.equals(AndroidBuild.TARGET_SDK)) { + continue; // Allows only the latest build tools for the target platform + } + + urlHolder.buildToolsVersion = major + "." + minor + "." + micro; + + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + + for (int j = 0; j < archives.getLength(); ++j) { + NodeList archive = archives.item(j).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList os = ((Element) archive).getElementsByTagName("host-os"); + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + if (os.item(0).getTextContent().equals(requiredHostOs)) { + urlHolder.buildToolsFilename = url.item(0).getTextContent(); + urlHolder.buildToolsUrl = REPOSITORY_URL + urlHolder.buildToolsFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + found = true; + break; + } + } + if (found) break; + } + } + if (!found) { + throw new IOException(AndroidMode.getTextString("sdk_downloader.error_cannot_find_build_tools")); + } + + // ----------------------------------------------------------------------- + // Command-line tools + expr = xpath.compile("//remotePackage[starts-with(@path, \"cmdline-tools;\")]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + found = false; + if (remotePackages != null) { + for (int tool = 0; tool < remotePackages.getLength(); tool++) { + NodeList childNodes = remotePackages.item(tool).getChildNodes(); + + NodeList channel = ((Element) childNodes).getElementsByTagName("channelRef"); + if (!channel.item(0).getAttributes().item(0).getNodeValue().equals("channel-0")) { + continue; // Stable channel only, skip others + } + + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + for (int i = 0; i < archives.getLength(); ++i) { + NodeList archive = archives.item(i).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList os = ((Element) archive).getElementsByTagName("host-os"); + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + if (os.item(0).getTextContent().equals(requiredHostOs)) { + urlHolder.cmdlineToolsFilename = url.item(0).getTextContent(); + urlHolder.cmdlineToolsUrl = REPOSITORY_URL + urlHolder.cmdlineToolsFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + found = true; + break; + } + } + if (found) break; + } + } + if (!found) { + throw new IOException(AndroidMode.getTextString("sdk_downloader.error_cannot_find_tools")); + } + + if (DOWNLOAD_EMU_WITH_SDK) { + // ----------------------------------------------------------------------- + // Emulator + expr = xpath.compile("//remotePackage[@path=\"emulator\"]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + found = false; + if (remotePackages != null) { + for (int i = 0; i < remotePackages.getLength(); ++i) { + NodeList childNodes = remotePackages.item(i).getChildNodes(); + + NodeList channel = ((Element) childNodes).getElementsByTagName("channelRef"); + if (!channel.item(0).getAttributes().item(0).getNodeValue().equals("channel-0")) { + continue; //Stable channel only, skip others + } + + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + + for (int j = 0; j < archives.getLength(); ++j) { + NodeList archive = archives.item(j).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList os = ((Element) archive).getElementsByTagName("host-os"); + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + if (os.item(0).getTextContent().equals(requiredHostOs)) { + urlHolder.emulatorFilename = url.item(0).getTextContent(); + urlHolder.emulatorUrl = REPOSITORY_URL + urlHolder.emulatorFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + found = true; + break; + } + } + if (found) break; + } + } + } + if (!found) { + throw new IOException(AndroidMode.getTextString("sdk_downloader.error_cannot_find_emulator")); + } + } + } + + private void getExtrasDownloadUrls(SDKUrlHolder urlHolder, + String repositoryUrl, String requiredHostOs) + throws ParserConfigurationException, IOException, SAXException, XPathException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new URL(repositoryUrl).openStream()); + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr; + NodeList remotePackages; + + // --------------------------------------------------------------------- + // Android Support repository + expr = xpath.compile("//remotePackage[@path=\"extras;android;m2repository\"]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (remotePackages != null) { + parseAndSet(urlHolder, remotePackages, requiredHostOs, ANDROID_REPO); + } + + // --------------------------------------------------------------------- + // Google repository + expr = xpath.compile("//remotePackage[@path=\"extras;google;m2repository\"]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (remotePackages != null) { + parseAndSet(urlHolder, remotePackages, requiredHostOs, GOOGLE_REPO); + } + + // --------------------------------------------------------------------- + // USB driver + expr = xpath.compile("//remotePackage[@path=\"extras;google;usb_driver\"]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (remotePackages != null && Platform.isWindows()) { + parseAndSet(urlHolder, remotePackages, requiredHostOs, USB_DRIVER); + } + } + + private void getHaxmDownloadUrl(SDKUrlHolder urlHolder, + String repositoryUrl, String requiredHostOs) + throws ParserConfigurationException, IOException, SAXException, XPathException { + if (requiredHostOs.equals("linux")) + return; + + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(new URL(repositoryUrl).openStream()); + + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr; + NodeList remotePackages; + + expr = xpath.compile("//remotePackage[@path=\"extras;intel;Hardware_Accelerated_Execution_Manager\"]"); + remotePackages = (NodeList) expr.evaluate(doc, XPathConstants.NODESET); + if (remotePackages != null) { + for (int i = 0; i < remotePackages.getLength(); ++i) { + NodeList childNodes = remotePackages.item(i).getChildNodes(); + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + + NodeList archive = archives.item(0).getChildNodes(); + NodeList os = ((Element) archive).getElementsByTagName("host-os"); + + if (!os.item(0).getTextContent().equals(requiredHostOs)) { + continue; + } + + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + urlHolder.haxmFilename = url.item(0).getTextContent(); + urlHolder.haxmUrl = HAXM_URL + urlHolder.haxmFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + break; + } + } + } + + private void parseAndSet(SDKUrlHolder urlHolder, NodeList remotePackages, String requiredHostOs, int packageN) { + NodeList childNodes = remotePackages.item(0).getChildNodes(); + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + + for (int i = 0; i < archives.getLength(); ++i) { + NodeList archive = archives.item(i).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + switch (packageN) { + case PLATFORM_TOOLS: + NodeList os = ((Element) archive).getElementsByTagName("host-os"); + if (!os.item(0).getTextContent().equals(requiredHostOs)) { + continue; + } + urlHolder.platformToolsFilename = url.item(0).getTextContent(); + urlHolder.platformToolsUrl = REPOSITORY_URL + urlHolder.platformToolsFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + break; + case USB_DRIVER: + urlHolder.usbDriverFilename = url.item(0).getTextContent(); + urlHolder.usbDriverUrl = REPOSITORY_URL + urlHolder.usbDriverFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + break; + } + break; + } + } + + private void renameFolder(File baseFolder, String expected, String actual) + throws IOException { + File expectedPath = new File(baseFolder, expected); + File actualPath = new File(baseFolder, actual); + if (!expectedPath.exists()) { + if (actualPath.exists()) { + actualPath.renameTo(expectedPath); + } else { + throw new IOException(AndroidMode.getTextString("sdk_downloader.error.cannot_unpack_platform", actualPath.getAbsolutePath())); + } + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(AndroidMode.getTextString("download_property.change_event_total"))) { + progressBar.setIndeterminate(false); + totalSize = (Integer) evt.getNewValue(); + progressBar.setMaximum(totalSize); + } else if (evt.getPropertyName().equals(AndroidMode.getTextString("download_property.change_event_downloaded"))) { + downloadedTextArea.setText(humanReadableByteCount((Integer) evt.getNewValue(), true) + + " / " + humanReadableByteCount(totalSize, true)); + progressBar.setValue((Integer) evt.getNewValue()); + } + } + + // http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + public SDKDownloader(Frame editor) { + super(editor, AndroidMode.getTextString("sdk_downloader.download_title"), true); + this.editor = editor; + this.sdk = null; + createLayout(); + } + + public void run() { + cancelled = false; + downloadTask = new SDKDownloadTask(); + downloadTask.addPropertyChangeListener(this); + downloadTask.execute(); + setAlwaysOnTop(true); + setVisible(true); + } + + public boolean cancelled() { + return cancelled; + } + + public AndroidSDK getSDK() { + return sdk; + } + + private void createLayout() { + Container outer = getContentPane(); + outer.removeAll(); + + Box vbox = Box.createVerticalBox(); + vbox.setBorder(new EmptyBorder(BOX_BORDER, BOX_BORDER, BOX_BORDER, BOX_BORDER)); + outer.add(vbox); + + String labelText = AndroidMode.getTextString("sdk_downloader.download_sdk_label"); + JLabel textarea = new JLabel(labelText); + textarea.setAlignmentX(LEFT_ALIGNMENT); + vbox.add(textarea); + + // Needed to put the progressBar inside this panel so we can set its size + JPanel progressPanel = new JPanel(); + BoxLayout boxLayout = new BoxLayout(progressPanel, BoxLayout.Y_AXIS); + progressPanel.setLayout(boxLayout); + progressBar = new JProgressBar(0, 100); + progressBar.setPreferredSize(new Dimension(BAR_WIDTH, BAR_HEIGHT)); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setIndeterminate(true); + progressBar.setBorder(new EmptyBorder(BAR_BORDER, BAR_BORDER, BAR_BORDER, BAR_BORDER)); + progressPanel.add(progressBar); + vbox.add(progressPanel); + + downloadedTextArea = new JLabel("0 / 0 MB"); + downloadedTextArea.setAlignmentX(LEFT_ALIGNMENT); + vbox.add(downloadedTextArea); + + vbox.add(Box.createVerticalStrut(GAP)); + + // buttons + JPanel buttons = new JPanel(); +// buttons.setPreferredSize(new Dimension(400, 35)); +// JPanel buttons = new JPanel() { +// public Dimension getPreferredSize() { +// return new Dimension(400, 35); +// } +// public Dimension getMinimumSize() { +// return new Dimension(400, 35); +// } +// public Dimension getMaximumSize() { +// return new Dimension(400, 35); +// } +// }; + +// Box buttons = Box.createHorizontalBox(); + buttons.setAlignmentX(LEFT_ALIGNMENT); + JButton cancelButton = new JButton(AndroidMode.getTextString("download_prompt.cancel")); + Dimension dim = new Dimension(Toolkit.getButtonWidth()*2, + Toolkit.zoom(cancelButton.getPreferredSize().height)); + + cancelButton.setPreferredSize(dim); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (downloadTask != null) { + downloadTask.cancel(true); + } + setVisible(false); + cancelled = true; + } + }); + cancelButton.setEnabled(true); + + buttons.add(cancelButton); +// buttons.setMaximumSize(new Dimension(300, buttons.getPreferredSize().height)); + vbox.add(buttons); + + JRootPane root = getRootPane(); + root.setDefaultButton(cancelButton); + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + Toolkit.registerWindowCloseKeys(root, disposer); + Toolkit.setIcon(this); + + pack(); + + setResizable(false); + setLocationRelativeTo(editor); + } +} \ No newline at end of file diff --git a/processing/mode/src/processing/mode/android/SysImageDownloader.java b/processing/mode/src/processing/mode/android/SysImageDownloader.java new file mode 100644 index 000000000..d665991f4 --- /dev/null +++ b/processing/mode/src/processing/mode/android/SysImageDownloader.java @@ -0,0 +1,571 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2016-21 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.mode.android; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import processing.app.Platform; +import processing.app.Preferences; +import processing.app.exec.LineProcessor; +import processing.app.exec.StreamPump; +import processing.app.ui.Toolkit; +import processing.core.PApplet; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkListener; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.*; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +@SuppressWarnings("serial") +public class SysImageDownloader extends JDialog implements PropertyChangeListener { + final static private int FONT_SIZE = Toolkit.zoom(11); + final static private int TEXT_MARGIN = Toolkit.zoom(8); + final static private int TEXT_WIDTH = Toolkit.zoom(300); + + private static final String SYS_IMAGES_ARM_URL = "https://dl.google.com/android/repository/sys-img/android/"; + + private static final String SYS_IMAGES_PHONE_URL = "https://dl.google.com/android/repository/sys-img/google_apis/"; + private static final String SYS_IMAGES_PHONE_LIST = "sys-img2-3.xml"; + + private static final String SYS_IMAGES_WEAR_URL = "https://dl.google.com/android/repository/sys-img/android-wear/"; + private static final String SYS_IMAGES_WEAR_LIST = "sys-img2-3.xml"; + + private static final String EMULATOR_GUIDE_URL = + "https://developer.android.com/studio/run/emulator-acceleration.html"; + + private static final String KVM_LINUX_GUIDE_URL = + "https://developer.android.com/studio/run/emulator-acceleration.html#vm-linux"; + + private JProgressBar progressBar; + private JLabel downloadedTextArea; + + private DownloadTask downloadTask; + + private Frame editor; + private boolean result; + private boolean wear; + private boolean askABI; + private String abi; + private boolean cancelled; + + private int totalSize = 0; + + class UrlHolder { + public String platformVersion; + public String sysImgUrl, sysImgTag, sysImgWearUrl, sysImgWearTag; + public String sysImgFilename, sysImgWearFilename; + public int totalSize = 0; + } + + class DownloadTask extends SwingWorker { + + private int downloadedSize = 0; + private int BUFFER_SIZE = 4096; + + @Override + protected Object doInBackground() throws Exception { + result = false; + + // The SDK should already be detected by the android mode + String sdkPrefsPath = Preferences.get("android.sdk.path"); + + File sketchbookFolder = processing.app.Base.getSketchbookFolder(); + File androidFolder = new File(sketchbookFolder, "android"); + if (!androidFolder.exists()) androidFolder.mkdir(); + + File sdkFolder = new File(sdkPrefsPath); + if (!sdkFolder.exists()) { + throw new IOException("SDK folder does not exist " + sdkFolder.getAbsolutePath()); + } + + // creating sdk folders + File sysImgFolder = new File(sdkFolder, "system-images"); + if (!sysImgFolder.exists()) sysImgFolder.mkdir(); + + // creating temp folder for downloaded zip packages + File tempFolder = new File(androidFolder, "temp"); + if (!tempFolder.exists()) tempFolder.mkdir(); + + try { + String repo; + if (wear) { + repo = SYS_IMAGES_WEAR_URL + SYS_IMAGES_WEAR_LIST; + } else if (abi.equals("arm")) { + // The ARM images using Google APIs are too slow, so use the + // older Android (AOSP) images. + repo = SYS_IMAGES_ARM_URL + SYS_IMAGES_PHONE_LIST; + } else { + repo = SYS_IMAGES_PHONE_URL + SYS_IMAGES_PHONE_LIST; + } + + UrlHolder downloadUrls = new UrlHolder(); + getDownloadUrls(downloadUrls, repo, Platform.getName()); + firePropertyChange(AndroidMode.getTextString("download_property.change_event_total"), 0, downloadUrls.totalSize); + totalSize = downloadUrls.totalSize; + + String level = AVD.getTargetSDK(wear, abi); + + if (wear) { + // wear system images + File downloadedSysImgWear = new File(tempFolder, downloadUrls.sysImgWearFilename); + File tmp = new File(sysImgFolder, "android-" + level); + if (!tmp.exists()) tmp.mkdir(); + File sysImgWearFinalFolder = new File(tmp, downloadUrls.sysImgWearTag); + if (!sysImgWearFinalFolder.exists()) sysImgWearFinalFolder.mkdir(); + downloadAndUnpack(downloadUrls.sysImgWearUrl, downloadedSysImgWear, sysImgWearFinalFolder); + fixSourceProperties(sysImgWearFinalFolder); + } else { + // mobile system images + File downloadedSysImg = new File(tempFolder, downloadUrls.sysImgFilename); + File tmp = new File(sysImgFolder, "android-" + level); + if (!tmp.exists()) tmp.mkdir(); + File sysImgFinalFolder = new File(tmp, downloadUrls.sysImgTag); + if (!sysImgFinalFolder.exists()) sysImgFinalFolder.mkdir(); + downloadAndUnpack(downloadUrls.sysImgUrl, downloadedSysImg, sysImgFinalFolder); + fixSourceProperties(sysImgFinalFolder); + } + + if (Platform.isLinux() || Platform.isMacOS()) { + Runtime.getRuntime().exec("chmod -R 755 " + sysImgFolder.getAbsolutePath()); + } + + for (File f: tempFolder.listFiles()) f.delete(); + tempFolder.delete(); + + if (Platform.isLinux() && Platform.getVariant().equals("64")) { + AndroidUtil.showMessage(AndroidMode.getTextString("sys_image_downloader.dialog.ia32libs_title"), AndroidMode.getTextString("sys_image_downloader.dialog.ia32libs_body")); + } + + result = true; + } catch (ParserConfigurationException e) { + // TODO Handle exceptions here somehow (ie show error message) + // and handle at least mkdir() results (above) + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void done() { + super.done(); + setVisible(false); + dispose(); + } + + private void downloadAndUnpack(String urlString, File saveTo, + File unpackTo) throws IOException { + URL url = null; + try { + url = new URL(urlString); + } catch (MalformedURLException e) { + e.printStackTrace(); + return; + } + URLConnection conn = url.openConnection(); + + InputStream inputStream = conn.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(saveTo); + + byte[] b = new byte[BUFFER_SIZE]; + int count; + while ((count = inputStream.read(b)) >= 0) { + outputStream.write(b, 0, count); + downloadedSize += count; + firePropertyChange(AndroidMode.getTextString("download_property.change_event_downloaded"), 0, downloadedSize); + } + outputStream.flush(); outputStream.close(); inputStream.close(); + + inputStream.close(); + outputStream.close(); + + AndroidUtil.extractFolder(saveTo, unpackTo); + } + + // For some reason the source.properties file includes Addon entries, + // and this breaks the image... + private void fixSourceProperties(File imageFolder) { + for (File d: imageFolder.listFiles()) { + // Should iterate over the installed archs (x86, etc) + if (d.isDirectory()) { + for (File f: d.listFiles()) { + if (PApplet.getExtension(f.getName()).equals("properties")) { + String[] linesIn = PApplet.loadStrings(f); + String concat = ""; + for (String l: linesIn) { + if (l.indexOf("Addon") == -1) concat += l + "\n"; + } + String[] linesOut = concat.split("\n"); + PApplet.saveStrings(f, linesOut); + } + } + } + } + } + + private void getDownloadUrls(UrlHolder urlHolder, + String repositoryUrl, String requiredHostOs) + throws ParserConfigurationException, IOException, SAXException, XPathException { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db = dbf.newDocumentBuilder(); + XPathFactory xPathfactory = XPathFactory.newInstance(); + XPath xpath = xPathfactory.newXPath(); + XPathExpression expr; + NodeList remotePackages; + + String targetSDK = AVD.getTargetSDK(wear, abi); + if (abi.equals("arm")) { + expr = xpath.compile("//remotePackage[contains(@path, '" + targetSDK + "')" + + "and contains(@path, \"armeabi-v7a\")]"); + } if (abi.equals("arm64-v8a")) { + expr = xpath.compile("//remotePackage[contains(@path, '" + targetSDK + "')" + + "and contains(@path, \"arm64-v8a\")]"); + } else { + expr = xpath.compile("//remotePackage[contains(@path, '" + targetSDK + "')" + + "and contains(@path, \"x86\")]"); + } + + if (wear) { + Document docSysImgWear = db.parse(new URL(repositoryUrl).openStream()); + + remotePackages = (NodeList) expr.evaluate(docSysImgWear, XPathConstants.NODESET); + NodeList childNodes = remotePackages.item(0).getChildNodes(); + NodeList typeDetails = ((Element) childNodes).getElementsByTagName("type-details"); + NodeList tag = ((Element) typeDetails.item(0)).getElementsByTagName("tag"); + NodeList id = ((Element) tag.item(0)).getElementsByTagName("id"); + urlHolder.sysImgWearTag = id.item(0).getTextContent(); + + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + NodeList archive = archives.item(0).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + urlHolder.sysImgWearFilename = url.item(0).getTextContent(); + urlHolder.sysImgWearUrl = SYS_IMAGES_WEAR_URL + urlHolder.sysImgWearFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + } else { + Document docSysImg = db.parse(new URL(repositoryUrl).openStream()); + remotePackages = (NodeList) expr.evaluate(docSysImg, XPathConstants.NODESET); + NodeList childNodes = remotePackages.item(0).getChildNodes(); // Index 1 contains x86_64 + + NodeList typeDetails = ((Element) childNodes).getElementsByTagName("type-details"); + //NodeList abi = ((Element) typeDetails.item(0)).getElementsByTagName("abi"); + //NodeList api = ((Element) typeDetails.item(0)).getElementsByTagName("api-level"); + //System.out.println(api.item(0).getTextContent()); + + NodeList tag = ((Element) typeDetails.item(0)).getElementsByTagName("tag"); + NodeList id = ((Element) tag.item(0)).getElementsByTagName("id"); + urlHolder.sysImgTag = id.item(0).getTextContent(); + + NodeList archives = ((Element) childNodes).getElementsByTagName("archive"); + NodeList archive = archives.item(0).getChildNodes(); + NodeList complete = ((Element) archive).getElementsByTagName("complete"); + + NodeList url = ((Element) complete.item(0)).getElementsByTagName("url"); + NodeList size = ((Element) complete.item(0)).getElementsByTagName("size"); + + urlHolder.sysImgFilename = url.item(0).getTextContent(); + String imgUrl = abi.equals("arm") ? SYS_IMAGES_ARM_URL : SYS_IMAGES_PHONE_URL; + urlHolder.sysImgUrl = imgUrl + urlHolder.sysImgFilename; + urlHolder.totalSize += Integer.parseInt(size.item(0).getTextContent()); + } + } + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals(AndroidMode.getTextString("download_property.change_event_total"))) { + progressBar.setIndeterminate(false); + totalSize = (Integer) evt.getNewValue(); + progressBar.setMaximum(totalSize); + } else if (evt.getPropertyName().equals(AndroidMode.getTextString("download_property.change_event_downloaded"))) { + downloadedTextArea.setText(humanReadableByteCount((Integer) evt.getNewValue(), true) + + " / " + humanReadableByteCount(totalSize, true)); + progressBar.setValue((Integer) evt.getNewValue()); + } + } + + // http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + static public int showSysImageMessage() { + String htmlString = " " + + " "; + htmlString += "

    " + AndroidMode.getTextString("sys_image_downloader.dialog.select_image_body", EMULATOR_GUIDE_URL) + "

    "; + String title = AndroidMode.getTextString("sys_image_downloader.dialog.select_image_title"); + JEditorPane pane = new JEditorPane("text/html", htmlString); + pane.addHyperlinkListener(new HyperlinkListener() { + @Override + public void hyperlinkUpdate(HyperlinkEvent e) { + if (e.getEventType().equals(HyperlinkEvent.EventType.ACTIVATED)) { + Platform.openURL(e.getURL().toString()); + } + } + }); + pane.setEditable(false); + JLabel label = new JLabel(); + pane.setBackground(label.getBackground()); + + String[] options = new String[] { + AndroidMode.getTextString("sys_image_downloader.option.x86_image"), + AndroidMode.getTextString("sys_image_downloader.option.arm_image") + }; + int result = JOptionPane.showOptionDialog(null, pane, title, + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, options, options[0]); + if (result == JOptionPane.YES_OPTION) { + return JOptionPane.YES_OPTION; + } else if (result == JOptionPane.NO_OPTION) { + return JOptionPane.NO_OPTION; + } else { + return JOptionPane.CLOSED_OPTION; + } + } + + public SysImageDownloader(Frame editor, boolean wear, boolean ask) { + super(editor, AndroidMode.getTextString("sys_image_downloader.download_title"), true); + this.editor = editor; + this.wear = wear; + this.askABI = ask; + this.result = false; + createLayout(); + } + + public void run() { + cancelled = false; + + abi = Preferences.get("android.emulator.image.abi"); + if (abi == null || askABI) { + // Either there was no image architecture selected, or the default was set. + // In this case, we give the user the option to choose between ARM and x86 + + int result = 0; + boolean arm64 = false; + // PROCESSOR_IDENTIFIER is only defined on Windows. For cross-platform CPU + // info, in the future we could use OSHI: https://github.com/oshi/oshi + String procId = System.getenv("PROCESSOR_IDENTIFIER"); + if (procId != null) { + if (-1 < procId.indexOf("Intel")) { + // Intel CPU: we go for the x86 abi + result = JOptionPane.YES_OPTION; + } else { + // Another CPU, can only be AMD, so we go for ARM abi + result = JOptionPane.NO_OPTION; + } + } else if (Platform.isMacOS()) { + if (Platform.getNativeArch().equals("aarch64")) { + // Apple Silicon Mac, so we go for the arm64 abi + arm64 = true; + result = JOptionPane.NO_OPTION; + } else { + // Intel Mac, so we go for the x86 abi + result = JOptionPane.YES_OPTION; + } + } else { + result = showSysImageMessage(); + } + if (result == JOptionPane.YES_OPTION || result == JOptionPane.CLOSED_OPTION) { + abi = "x86"; + installHAXM(); + } else if (arm64) { + abi = "arm64-v8a"; + } else { + abi = "arm"; + } + Preferences.set("android.emulator.image.abi", abi); + } + + downloadTask = new DownloadTask(); + downloadTask.addPropertyChangeListener(this); + downloadTask.execute(); + setAlwaysOnTop(true); + setVisible(true); + } + + public boolean cancelled() { + return cancelled; + } + + public boolean getResult() { + return result; + } + + static public void installHAXM() { + File haxmFolder = AndroidSDK.getHAXMInstallerFolder(); + if (Platform.isLinux()) { + AndroidUtil.showMessage(AndroidMode.getTextString("sys_image_downloader.dialog.accel_images_title"), + AndroidMode.getTextString("sys_image_downloader.dialog.kvm_config_body", KVM_LINUX_GUIDE_URL)); + } else if (haxmFolder.exists()) { + AndroidUtil.showMessage(AndroidMode.getTextString("sys_image_downloader.dialog.accel_images_title"), + AndroidMode.getTextString("sys_image_downloader.dialog.haxm_install_body")); + + ProcessBuilder pb; + if (Platform.isWindows()) { + File exec = new File(haxmFolder, "silent_install.bat"); + pb = new ProcessBuilder(exec.getAbsolutePath()); + } else { + File exec = new File(haxmFolder, "HAXM installation"); + pb = new ProcessBuilder(exec.getAbsolutePath()); + } + pb.directory(haxmFolder); + pb.redirectErrorStream(true); + + Process process = null; + try { + process = pb.start(); + } catch (IOException e) { + e.printStackTrace(); + } + + if (process != null) { + try { + StreamPump output = new StreamPump(process.getInputStream(), "HAXM: "); + output.addTarget(new LineProcessor() { + @Override + public void processLine(String line) { + System.out.println("HAXM: " + line); + } + }).start(); + + process.waitFor(); + } catch (final InterruptedException ie) { + ie.printStackTrace(); + System.out.println("Processing was not able to install HAXM automatically, " + + "but the installation package was downloaded into android/sdk/extras/intel/HAXM. " + + "You can try install to install it manually from there."); + } finally { + process.destroy(); + } + } + } + } + + private void createLayout() { + Container outer = getContentPane(); + outer.removeAll(); + + Box pain = Box.createVerticalBox(); + pain.setBorder(new EmptyBorder(13, 13, 13, 13)); + outer.add(pain); + + String labelText = wear ? AndroidMode.getTextString("sys_image_downloader.download_watch_label") : + AndroidMode.getTextString("sys_image_downloader.download_phone_label"); + JLabel textarea = new JLabel(labelText); + textarea.setAlignmentX(LEFT_ALIGNMENT); + pain.add(textarea); + + progressBar = new JProgressBar(0, 100); + progressBar.setValue(0); + progressBar.setStringPainted(true); + progressBar.setIndeterminate(true); + progressBar.setBorder(new EmptyBorder(10, 10, 10, 10) ); + pain.add(progressBar); + + downloadedTextArea = new JLabel(""); + downloadedTextArea.setAlignmentX(LEFT_ALIGNMENT); + pain.add(downloadedTextArea); + + // buttons + JPanel buttons = new JPanel(); +// buttons.setPreferredSize(new Dimension(400, 35)); +// JPanel buttons = new JPanel() { +// public Dimension getPreferredSize() { +// return new Dimension(400, 35); +// } +// public Dimension getMinimumSize() { +// return new Dimension(400, 35); +// } +// public Dimension getMaximumSize() { +// return new Dimension(400, 35); +// } +// }; + +// Box buttons = Box.createHorizontalBox(); + buttons.setAlignmentX(LEFT_ALIGNMENT); + JButton cancelButton = new JButton(AndroidMode.getTextString("download_prompt.cancel")); + Dimension dim = new Dimension(Toolkit.getButtonWidth()*2, + cancelButton.getPreferredSize().height); + + cancelButton.setPreferredSize(dim); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (downloadTask != null) { + downloadTask.cancel(true); + } + setVisible(false); + cancelled = true; + } + }); + cancelButton.setEnabled(true); + + buttons.add(cancelButton); +// buttons.setMaximumSize(new Dimension(300, buttons.getPreferredSize().height)); + pain.add(buttons); + + JRootPane root = getRootPane(); + root.setDefaultButton(cancelButton); + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }; + Toolkit.registerWindowCloseKeys(root, disposer); + Toolkit.setIcon(this); + + pack(); + + setResizable(false); + setLocationRelativeTo(editor); + } +} diff --git a/processing/mode/templates/ARActivity.java.tmpl b/processing/mode/templates/ARActivity.java.tmpl new file mode 100644 index 000000000..ca441712f --- /dev/null +++ b/processing/mode/templates/ARActivity.java.tmpl @@ -0,0 +1,107 @@ +package @@package_name@@; + +import android.Manifest; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.content.Intent; +import android.provider.Settings; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import processing.android.PFragment; +import processing.android.CompatUtils; +import processing.core.PApplet; + +public class MainActivity extends AppCompatActivity { + private static final int CAMERA_PERMISSION_CODE = 0; + private static boolean CAMERA_PERMISSION_REQUESTED = false; + private static final String CAMERA_PERMISSION = Manifest.permission.CAMERA; + private static final String CAMERA_PERMISSION_MESSAGE = "Camera permission is needed to use AR"; + + private PApplet sketch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FrameLayout frame = new FrameLayout(this); + frame.setId(CompatUtils.getUniqueViewId()); + setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + sketch = new @@sketch_class_name@@(); + @@external@@ + PFragment fragment = new PFragment(sketch); + fragment.setView(frame, this); + } + + @Override + protected void onResume() { + super.onResume(); + if (!hasCameraPermission()) requestCameraPermission(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (!hasCameraPermission()) { + Toast.makeText(this, CAMERA_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); + if (!shouldShowRequestPermissionRationale()) { + launchPermissionSettings(); + } + finish(); + } + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + CAMERA_PERMISSION_REQUESTED = false; + } + + @Override + public void onNewIntent(Intent intent) { + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } + + private boolean hasCameraPermission() { + int res = ContextCompat.checkSelfPermission(this, CAMERA_PERMISSION); + return res == PackageManager.PERMISSION_GRANTED; + } + + private void requestCameraPermission() { + if (!CAMERA_PERMISSION_REQUESTED) { + CAMERA_PERMISSION_REQUESTED = true; + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, CAMERA_PERMISSION_CODE); + } + } + + private boolean shouldShowRequestPermissionRationale() { + return ActivityCompat.shouldShowRequestPermissionRationale(this, CAMERA_PERMISSION); + } + + private void launchPermissionSettings() { + Intent intent = new Intent(); + intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", this.getPackageName(), null)); + this.startActivity(intent); + } +} diff --git a/processing/mode/templates/ARBuild.gradle.tmpl b/processing/mode/templates/ARBuild.gradle.tmpl new file mode 100644 index 000000000..9bd31923a --- /dev/null +++ b/processing/mode/templates/ARBuild.gradle.tmpl @@ -0,0 +1,54 @@ +apply plugin: 'com.android.application' + +android { + sourceSets { + main.jni.srcDirs = [] + main.jniLibs.srcDirs = ['libs'] + } + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:@@appcompat_version@@' + implementation 'com.google.ar:core:@@gar_version@@' + implementation files('libs/processing-core.jar') + implementation files('libs/ar.jar') + androidTestImplementation 'com.android.support.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13' +} diff --git a/processing/mode/templates/ARBuildECJ.gradle.tmpl b/processing/mode/templates/ARBuildECJ.gradle.tmpl new file mode 100644 index 000000000..45e72f569 --- /dev/null +++ b/processing/mode/templates/ARBuildECJ.gradle.tmpl @@ -0,0 +1,100 @@ +apply plugin: 'com.android.application' + +android { + sourceSets { + main.jni.srcDirs = [] + main.jniLibs.srcDirs = ['libs'] + } + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + + // We create a variant of the compile task, where we use the Eclipse Compiler for Java + // (ECJ) instead of the JDK (which would require the user to download and install + // the Oracle JDK). Inspired by the following: + // https://github.com/bytedeco/javacpp/wiki/Gradle + // https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html + applicationVariants.all { variant -> + variant.javaCompileProvider.get().doFirst { + // The main class that runs the Eclipse batch compiler + String ecjMain = 'org.eclipse.jdt.internal.compiler.batch.Main' + + // We construct the list of arguments needed by the batch compiler + // https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_batch_compiler.htm + List ecjArgs = ['-nowarn', + '-source', variant.javaCompileProvider.get().sourceCompatibility, + '-target', variant.javaCompileProvider.get().targetCompatibility, + '-d', variant.javaCompileProvider.get().destinationDir] as String[] + + // Set the debug attributes level according to the build target + if (variant.name == 'debug') { + // All debug info + ecjArgs += '-g' + } else { + // No debug info + ecjArgs += '-g:none' + } + + // Adding all the source files to the list of arguments + ecjArgs += variant.javaCompileProvider.get().source + + // Add the Android jar to the classpath inherited from the task + FileCollection ecjClasspath = files('@@target_platform@@/android.jar', + variant.javaCompileProvider.get().classpath) + + // Running the JavaExec task, which requires the main class to run, + // the classpath, and the list of arguments + // https://docs.gradle.org/3.5/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:main + javaexec { + main ecjMain + classpath ecjClasspath + args ecjArgs + } + + // We skip the rest of the compileXxxJavaWithJavac task, since we + // source is already compiled with ecj + throw new StopExecutionException("skip javac") + } + } +} + +dependencies { + compileOnly files('@@mode_folder@@/org.eclipse.jdt.core.jar') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:@@appcompat_version@@' + implementation 'com.google.ar:core:@@gar_version@@' + implementation files('libs/processing-core.jar') + implementation files('libs/ar.jar') +} diff --git a/processing/mode/templates/ARManifest.xml.tmpl b/processing/mode/templates/ARManifest.xml.tmpl new file mode 100644 index 000000000..1b31e497b --- /dev/null +++ b/processing/mode/templates/ARManifest.xml.tmpl @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + diff --git a/processing/mode/templates/AppActivity.java.tmpl b/processing/mode/templates/AppActivity.java.tmpl new file mode 100644 index 000000000..3e24984b4 --- /dev/null +++ b/processing/mode/templates/AppActivity.java.tmpl @@ -0,0 +1,57 @@ +package @@package_name@@; + +import android.os.Bundle; +import android.content.Intent; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import androidx.appcompat.app.AppCompatActivity; + +import processing.android.PFragment; +import processing.android.CompatUtils; +import processing.core.PApplet; + +public class MainActivity extends AppCompatActivity { + private PApplet sketch; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + FrameLayout frame = new FrameLayout(this); + frame.setId(CompatUtils.getUniqueViewId()); + setContentView(frame, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + sketch = new @@sketch_class_name@@(); + @@external@@ + PFragment fragment = new PFragment(sketch); + fragment.setView(frame, this); + } + + @Override + public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + if (sketch != null) { + sketch.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + } + + @Override + public void onNewIntent(Intent intent) { + if (sketch != null) { + sketch.onNewIntent(intent); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (sketch != null) { + sketch.onActivityResult(requestCode, resultCode, data); + } + } + + @Override + public void onBackPressed() { + if (sketch != null) { + sketch.onBackPressed(); + } + } +} diff --git a/processing/mode/templates/AppBuild.gradle.tmpl b/processing/mode/templates/AppBuild.gradle.tmpl new file mode 100644 index 000000000..7774d0368 --- /dev/null +++ b/processing/mode/templates/AppBuild.gradle.tmpl @@ -0,0 +1,59 @@ +apply plugin: 'com.android.application' + +android { + sourceSets { + main.jni.srcDirs = [] + main.jniLibs.srcDirs = ['libs'] + } + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + vectorDrawables.useSupportLibrary = true + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + aaptOptions { + noCompress "tflite" + noCompress "lite" + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:@@appcompat_version@@' + implementation files('libs/processing-core.jar') + androidTestImplementation 'com.android.support.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13' +} + diff --git a/processing/mode/templates/AppBuildECJ.gradle.tmpl b/processing/mode/templates/AppBuildECJ.gradle.tmpl new file mode 100644 index 000000000..6e2d7f055 --- /dev/null +++ b/processing/mode/templates/AppBuildECJ.gradle.tmpl @@ -0,0 +1,104 @@ +apply plugin: 'com.android.application' + +android { + sourceSets { + main.jni.srcDirs = [] + main.jniLibs.srcDirs = ['libs'] + } + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + vectorDrawables.useSupportLibrary = true + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + aaptOptions { + noCompress "tflite" + noCompress "lite" + } + + // We create a variant of the compile task, where we use the Eclipse Compiler for Java + // (ECJ) instead of the JDK (which would require the user to download and install + // the Oracle JDK). Inspired by the following: + // https://github.com/bytedeco/javacpp/wiki/Gradle + // https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html + applicationVariants.all { variant -> + variant.javaCompileProvider.get().doFirst { + // The main class that runs the Eclipse batch compiler + String ecjMain = 'org.eclipse.jdt.internal.compiler.batch.Main' + + // We construct the list of arguments needed by the batch compiler + // https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_batch_compiler.htm + List ecjArgs = ['-nowarn', + '-source', variant.javaCompileProvider.get().sourceCompatibility, + '-target', variant.javaCompileProvider.get().targetCompatibility, + '-d', variant.javaCompileProvider.get().destinationDir] as String[] + + // Set the debug attributes level according to the build target + if (variant.name == 'debug') { + // All debug info + ecjArgs += '-g' + } else { + // No debug info + ecjArgs += '-g:none' + } + + // Adding all the source files to the list of arguments + ecjArgs += variant.javaCompileProvider.get().source + + // Add the Android jar to the classpath inherited from the task + FileCollection ecjClasspath = files('@@target_platform@@/android.jar', + variant.javaCompileProvider.get().classpath) + + // Running the JavaExec task, which requires the main class to run, + // the classpath, and the list of arguments + // https://docs.gradle.org/4.4/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:main + javaexec { + main ecjMain + classpath ecjClasspath + args ecjArgs + } + + // We skip the rest of the compileXxxJavaWithJavac task, since we + // source is already compiled with ecj + throw new StopExecutionException("skip javac") + } + } +} + +dependencies { + compileOnly files('@@mode_folder@@/org.eclipse.jdt.core.jar') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:@@appcompat_version@@' + implementation files('libs/processing-core.jar') +} diff --git a/processing/mode/templates/AppManifest.xml.tmpl b/processing/mode/templates/AppManifest.xml.tmpl new file mode 100644 index 000000000..4f1abe5ef --- /dev/null +++ b/processing/mode/templates/AppManifest.xml.tmpl @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/processing/mode/templates/ExpProperties.gradle.tmpl b/processing/mode/templates/ExpProperties.gradle.tmpl new file mode 100644 index 000000000..777b6aad8 --- /dev/null +++ b/processing/mode/templates/ExpProperties.gradle.tmpl @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536m +android.enableJetifier=true +android.useAndroidX=true \ No newline at end of file diff --git a/processing/mode/templates/LayoutActivity.xml.tmpl b/processing/mode/templates/LayoutActivity.xml.tmpl new file mode 100644 index 000000000..012ad5a34 --- /dev/null +++ b/processing/mode/templates/LayoutActivity.xml.tmpl @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/processing/mode/templates/Properties.gradle.tmpl b/processing/mode/templates/Properties.gradle.tmpl new file mode 100644 index 000000000..6511be76b --- /dev/null +++ b/processing/mode/templates/Properties.gradle.tmpl @@ -0,0 +1,12 @@ +org.gradle.jvmargs=-Xmx1536m +android.enableJetifier=true +android.useAndroidX=true + +PROCESSING_UPLOAD_KEYSTORE_FILE=@@keystore_file@@ +PROCESSING_UPLOAD_KEY_ALIAS=@@key_alias@@ +PROCESSING_UPLOAD_STORE_PASSWORD=@@key_password@@ +PROCESSING_UPLOAD_KEY_PASSWORD=@@key_password@@ + + + + diff --git a/processing/mode/templates/Properties.local.tmpl b/processing/mode/templates/Properties.local.tmpl new file mode 100644 index 000000000..ee51efb73 --- /dev/null +++ b/processing/mode/templates/Properties.local.tmpl @@ -0,0 +1 @@ +sdk.dir=@@sdk_path@@ \ No newline at end of file diff --git a/processing/mode/templates/Settings.gradle.tmpl b/processing/mode/templates/Settings.gradle.tmpl new file mode 100644 index 000000000..e6bbec22c --- /dev/null +++ b/processing/mode/templates/Settings.gradle.tmpl @@ -0,0 +1 @@ +include @@project_modules@@ diff --git a/processing/mode/templates/StringsWallpaper.xml.tmpl b/processing/mode/templates/StringsWallpaper.xml.tmpl new file mode 100644 index 000000000..dccf2cf3c --- /dev/null +++ b/processing/mode/templates/StringsWallpaper.xml.tmpl @@ -0,0 +1,3 @@ + + @@sketch_class_name@@ + \ No newline at end of file diff --git a/processing/mode/templates/StylesAR.xml.tmpl b/processing/mode/templates/StylesAR.xml.tmpl new file mode 100644 index 000000000..38b270523 --- /dev/null +++ b/processing/mode/templates/StylesAR.xml.tmpl @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/processing/mode/templates/StylesFragment.xml.tmpl b/processing/mode/templates/StylesFragment.xml.tmpl new file mode 100644 index 000000000..2ae9b9544 --- /dev/null +++ b/processing/mode/templates/StylesFragment.xml.tmpl @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/processing/mode/templates/StylesVR.xml.tmpl b/processing/mode/templates/StylesVR.xml.tmpl new file mode 100644 index 000000000..30903bcbd --- /dev/null +++ b/processing/mode/templates/StylesVR.xml.tmpl @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/processing/mode/templates/TopBuild.gradle.tmpl b/processing/mode/templates/TopBuild.gradle.tmpl new file mode 100644 index 000000000..ef01cc08e --- /dev/null +++ b/processing/mode/templates/TopBuild.gradle.tmpl @@ -0,0 +1,29 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:@@gradle_plugin_version@@' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + maven { url "https://maven.google.com" } + maven { url "https://jitpack.io" } + maven { url 'https://repo.gradle.org/gradle/libs-releases' } + google() + mavenCentral() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/processing/mode/templates/VRActivity.java.tmpl b/processing/mode/templates/VRActivity.java.tmpl new file mode 100644 index 000000000..bd2b73cce --- /dev/null +++ b/processing/mode/templates/VRActivity.java.tmpl @@ -0,0 +1,24 @@ +package @@package_name@@; + +import android.os.Build; +import android.os.Bundle; +import android.view.WindowManager; + +import processing.vr.VRActivity; +import processing.core.PApplet; + +public class MainActivity extends VRActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + // This is to ensure that the app shows in fullscreen mode with display cutout: + // https://stackoverflow.com/questions/49190381/fullscreen-app-with-displaycutout + getWindow().setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + getWindow().getAttributes().layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + } + PApplet sketch = new @@sketch_class_name@@(); + @@external@@ + setSketch(sketch); + } +} \ No newline at end of file diff --git a/processing/mode/templates/VRBuild.gradle.tmpl b/processing/mode/templates/VRBuild.gradle.tmpl new file mode 100644 index 000000000..3837b763a --- /dev/null +++ b/processing/mode/templates/VRBuild.gradle.tmpl @@ -0,0 +1,59 @@ +apply plugin: 'com.android.application' + +android { + sourceSets { + main.jni.srcDirs = [] + main.jniLibs.srcDirs = ['libs'] + } + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + aaptOptions { + noCompress "tflite" + noCompress "lite" + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation 'androidx.appcompat:appcompat:@@appcompat_version@@' + implementation project('libs:google-vr') + implementation files('libs/processing-core.jar') + implementation files('libs/vr.jar') + implementation 'com.google.protobuf.nano:protobuf-javanano:3.1.0' + androidTestImplementation 'com.android.support.test:runner:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + testImplementation 'junit:junit:4.13' +} diff --git a/processing/mode/templates/VRBuildECJ.gradle.tmpl b/processing/mode/templates/VRBuildECJ.gradle.tmpl new file mode 100644 index 000000000..d8368f00f --- /dev/null +++ b/processing/mode/templates/VRBuildECJ.gradle.tmpl @@ -0,0 +1,105 @@ +apply plugin: 'com.android.application' + +android { + sourceSets { + main.jni.srcDirs = [] + main.jniLibs.srcDirs = ['libs'] + } + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + aaptOptions { + noCompress "tflite" + noCompress "lite" + } + + // We create a variant of the compile task, where we use the Eclipse Compiler for Java + // (ECJ) instead of the JDK (which would require the user to download and install + // the Oracle JDK). Inspired by the following: + // https://github.com/bytedeco/javacpp/wiki/Gradle + // https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html + applicationVariants.all { variant -> + variant.javaCompileProvider.get().doFirst { + // The main class that runs the Eclipse batch compiler + String ecjMain = 'org.eclipse.jdt.internal.compiler.batch.Main' + + // We construct the list of arguments needed by the batch compiler + // https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_batch_compiler.htm + List ecjArgs = ['-nowarn', + '-source', variant.javaCompileProvider.get().sourceCompatibility, + '-target', variant.javaCompileProvider.get().targetCompatibility, + '-d', variant.javaCompileProvider.get().destinationDir] as String[] + + // Set the debug attributes level according to the build target + if (variant.name == 'debug') { + // All debug info + ecjArgs += '-g' + } else { + // No debug info + ecjArgs += '-g:none' + } + + // Adding all the source files to the list of arguments + ecjArgs += variant.javaCompileProvider.get().source + + // Add the Android jar to the classpath inherited from the task + FileCollection ecjClasspath = files('@@target_platform@@/android.jar', + variant.javaCompileProvider.get().classpath) + + // Running the JavaExec task, which requires the main class to run, + // the classpath, and the list of arguments + // https://docs.gradle.org/3.5/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:main + javaexec { + main ecjMain + classpath ecjClasspath + args ecjArgs + } + + // We skip the rest of the compileXxxJavaWithJavac task, since we + // source is already compiled with ecj + throw new StopExecutionException("skip javac") + } + } +} + +dependencies { + compileOnly files('@@mode_folder@@/org.eclipse.jdt.core.jar') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:@@appcompat_version@@' + implementation project('libs:google-vr') + implementation files('libs/processing-core.jar') + implementation files('libs/vr.jar') + implementation 'com.google.protobuf.nano:protobuf-javanano:3.1.0' +} diff --git a/processing/mode/templates/VRManifest.xml.tmpl b/processing/mode/templates/VRManifest.xml.tmpl new file mode 100644 index 000000000..0c550cfa0 --- /dev/null +++ b/processing/mode/templates/VRManifest.xml.tmpl @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/processing/mode/templates/WallpaperManifest.xml.tmpl b/processing/mode/templates/WallpaperManifest.xml.tmpl new file mode 100644 index 000000000..baec25eb5 --- /dev/null +++ b/processing/mode/templates/WallpaperManifest.xml.tmpl @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/processing/mode/templates/WallpaperService.java.tmpl b/processing/mode/templates/WallpaperService.java.tmpl new file mode 100644 index 000000000..f38471b68 --- /dev/null +++ b/processing/mode/templates/WallpaperService.java.tmpl @@ -0,0 +1,13 @@ +package @@package_name@@; + +import processing.android.PWallpaper; +import processing.core.PApplet; + +public class MainService extends PWallpaper { + @Override + public PApplet createSketch() { + PApplet sketch = new @@sketch_class_name@@(); + @@external@@ + return sketch; + } +} \ No newline at end of file diff --git a/processing/mode/templates/WatchFaceManifest.xml.tmpl b/processing/mode/templates/WatchFaceManifest.xml.tmpl new file mode 100644 index 000000000..28d8119da --- /dev/null +++ b/processing/mode/templates/WatchFaceManifest.xml.tmpl @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/processing/mode/templates/WatchFaceService.java.tmpl b/processing/mode/templates/WatchFaceService.java.tmpl new file mode 100644 index 000000000..ab322ba86 --- /dev/null +++ b/processing/mode/templates/WatchFaceService.java.tmpl @@ -0,0 +1,13 @@ +package @@package_name@@; + +import processing.android.@@watchface_classs@@; +import processing.core.PApplet; + +public class MainService extends @@watchface_classs@@ { + @Override + public PApplet createSketch() { + PApplet sketch = new @@sketch_class_name@@(); + @@external@@ + return sketch; + } +} \ No newline at end of file diff --git a/processing/mode/templates/WearBuild.gradle.tmpl b/processing/mode/templates/WearBuild.gradle.tmpl new file mode 100644 index 000000000..25b37049b --- /dev/null +++ b/processing/mode/templates/WearBuild.gradle.tmpl @@ -0,0 +1,51 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + aaptOptions { + noCompress "tflite" + noCompress "lite" + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.google.android.gms:play-services-wearable:@@play_services_version@@' + implementation 'com.google.android.support:wearable:@@wear_version@@' + compileOnly 'com.google.android.wearable:wearable:@@wear_version@@' + implementation files('libs/processing-core.jar') +} diff --git a/processing/mode/templates/WearBuildECJ.gradle.tmpl b/processing/mode/templates/WearBuildECJ.gradle.tmpl new file mode 100644 index 000000000..518aa96d8 --- /dev/null +++ b/processing/mode/templates/WearBuildECJ.gradle.tmpl @@ -0,0 +1,104 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion @@target_sdk@@ + defaultConfig { + applicationId "@@package_name@@" + minSdkVersion @@min_sdk@@ + targetSdkVersion @@target_sdk@@ + versionCode @@version_code@@ + versionName "@@version_name@@" + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + signingConfigs { + release { + if (project.hasProperty('PROCESSING_UPLOAD_KEYSTORE_FILE')) { + storeFile file(PROCESSING_UPLOAD_KEYSTORE_FILE) + storePassword PROCESSING_UPLOAD_STORE_PASSWORD + keyAlias PROCESSING_UPLOAD_KEY_ALIAS + keyPassword PROCESSING_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + debuggable true + } + release { + minifyEnabled false + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + aaptOptions { + noCompress "tflite" + noCompress "lite" + } + + // We create a variant of the compile task, where we use the Eclipse Compiler for Java + // (ECJ) instead of the JDK (which would require the user to download and install + // the Oracle JDK). Inspired by the following: + // https://github.com/bytedeco/javacpp/wiki/Gradle + // https://docs.gradle.org/current/dsl/org.gradle.api.tasks.compile.JavaCompile.html + applicationVariants.all { variant -> + variant.javaCompileProvider.get().doFirst { + // The main class that runs the Eclipse batch compiler + String ecjMain = 'org.eclipse.jdt.internal.compiler.batch.Main' + + // We construct the list of arguments needed by the batch compiler + // https://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftask-using_batch_compiler.htm + List ecjArgs = ['-nowarn', + '-source', variant.javaCompileProvider.get().sourceCompatibility, + '-target', variant.javaCompileProvider.get().targetCompatibility, + '-d', variant.javaCompileProvider.get().destinationDir] as String[] + + // Set the debug attributes level according to the build target + if (variant.name == 'debug') { + // All debug info + ecjArgs += '-g' + } else { + // No debug info + ecjArgs += '-g:none' + } + + // Adding all the source files to the list of arguments + ecjArgs += variant.javaCompileProvider.get().source + + // Add the Android jar to the classpath inherited from the task + FileCollection ecjClasspath = files('@@target_platform@@/android.jar', + variant.javaCompileProvider.get().classpath) + + // Running the JavaExec task, which requires the main class to run, + // the classpath, and the list of arguments + // https://docs.gradle.org/3.5/dsl/org.gradle.api.tasks.JavaExec.html#org.gradle.api.tasks.JavaExec:main + javaexec { + main ecjMain + classpath ecjClasspath + args ecjArgs + } + + // We skip the rest of the compileXxxJavaWithJavac task, since we + // source is already compiled with ecj + throw new StopExecutionException("skip javac") + } + } +} + +dependencies { + compileOnly files('@@mode_folder@@/org.eclipse.jdt.core.jar') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.google.android.gms:play-services-wearable:@@play_services_version@@' + implementation 'com.google.android.support:wearable:@@wear_version@@' + compileOnly 'com.google.android.wearable:wearable:@@wear_version@@' + implementation files('libs/processing-core.jar') +} + + + + \ No newline at end of file diff --git a/processing/mode/templates/XMLWallpaper.xml.tmpl b/processing/mode/templates/XMLWallpaper.xml.tmpl new file mode 100644 index 000000000..4c5057c56 --- /dev/null +++ b/processing/mode/templates/XMLWallpaper.xml.tmpl @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/processing/mode/templates/XMLWatchFace.xml.tmpl b/processing/mode/templates/XMLWatchFace.xml.tmpl new file mode 100644 index 000000000..27199e957 --- /dev/null +++ b/processing/mode/templates/XMLWatchFace.xml.tmpl @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/processing/mode/theme/completion/class_obj-1x.png b/processing/mode/theme/completion/class_obj-1x.png new file mode 100644 index 000000000..7ea06bb70 Binary files /dev/null and b/processing/mode/theme/completion/class_obj-1x.png differ diff --git a/processing/mode/theme/completion/class_obj-2x.png b/processing/mode/theme/completion/class_obj-2x.png new file mode 100644 index 000000000..0f7b25331 Binary files /dev/null and b/processing/mode/theme/completion/class_obj-2x.png differ diff --git a/processing/mode/theme/completion/field_default_obj-1x.png b/processing/mode/theme/completion/field_default_obj-1x.png new file mode 100644 index 000000000..10f8e61c5 Binary files /dev/null and b/processing/mode/theme/completion/field_default_obj-1x.png differ diff --git a/processing/mode/theme/completion/field_default_obj-2x.png b/processing/mode/theme/completion/field_default_obj-2x.png new file mode 100644 index 000000000..093ae9c8a Binary files /dev/null and b/processing/mode/theme/completion/field_default_obj-2x.png differ diff --git a/processing/mode/theme/completion/field_protected_obj-1x.png b/processing/mode/theme/completion/field_protected_obj-1x.png new file mode 100644 index 000000000..4858d93e6 Binary files /dev/null and b/processing/mode/theme/completion/field_protected_obj-1x.png differ diff --git a/processing/mode/theme/completion/field_protected_obj-2x.png b/processing/mode/theme/completion/field_protected_obj-2x.png new file mode 100644 index 000000000..bdd9ab600 Binary files /dev/null and b/processing/mode/theme/completion/field_protected_obj-2x.png differ diff --git a/processing/mode/theme/completion/methpub_obj-1x.png b/processing/mode/theme/completion/methpub_obj-1x.png new file mode 100644 index 000000000..7e9e3aeee Binary files /dev/null and b/processing/mode/theme/completion/methpub_obj-1x.png differ diff --git a/processing/mode/theme/completion/methpub_obj-2x.png b/processing/mode/theme/completion/methpub_obj-2x.png new file mode 100644 index 000000000..cf64cb75b Binary files /dev/null and b/processing/mode/theme/completion/methpub_obj-2x.png differ diff --git a/processing/mode/theme/debug/breakpoint-enabled-1x.png b/processing/mode/theme/debug/breakpoint-enabled-1x.png new file mode 100644 index 000000000..db540e8b2 Binary files /dev/null and b/processing/mode/theme/debug/breakpoint-enabled-1x.png differ diff --git a/processing/mode/theme/debug/breakpoint-enabled-2x.png b/processing/mode/theme/debug/breakpoint-enabled-2x.png new file mode 100644 index 000000000..426ddbf75 Binary files /dev/null and b/processing/mode/theme/debug/breakpoint-enabled-2x.png differ diff --git a/processing/mode/theme/debug/continue-enabled-1x.png b/processing/mode/theme/debug/continue-enabled-1x.png new file mode 100644 index 000000000..f43171df4 Binary files /dev/null and b/processing/mode/theme/debug/continue-enabled-1x.png differ diff --git a/processing/mode/theme/debug/continue-enabled-2x.png b/processing/mode/theme/debug/continue-enabled-2x.png new file mode 100644 index 000000000..49c3687b9 Binary files /dev/null and b/processing/mode/theme/debug/continue-enabled-2x.png differ diff --git a/processing/mode/theme/debug/step-enabled-1x.png b/processing/mode/theme/debug/step-enabled-1x.png new file mode 100644 index 000000000..2217cd8b3 Binary files /dev/null and b/processing/mode/theme/debug/step-enabled-1x.png differ diff --git a/processing/mode/theme/debug/step-enabled-2x.png b/processing/mode/theme/debug/step-enabled-2x.png new file mode 100644 index 000000000..b4e0f329d Binary files /dev/null and b/processing/mode/theme/debug/step-enabled-2x.png differ diff --git a/processing/mode/theme/variables-1x.png b/processing/mode/theme/variables-1x.png new file mode 100644 index 000000000..b20038b48 Binary files /dev/null and b/processing/mode/theme/variables-1x.png differ diff --git a/processing/mode/theme/variables-2x.png b/processing/mode/theme/variables-2x.png new file mode 100644 index 000000000..134701d1e Binary files /dev/null and b/processing/mode/theme/variables-2x.png differ diff --git a/processing/mode/tools/SDKUpdater/.classpath b/processing/mode/tools/SDKUpdater/.classpath new file mode 100644 index 000000000..e79c28a0d --- /dev/null +++ b/processing/mode/tools/SDKUpdater/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/processing/mode/tools/SDKUpdater/.gitignore b/processing/mode/tools/SDKUpdater/.gitignore new file mode 100644 index 000000000..de7299333 --- /dev/null +++ b/processing/mode/tools/SDKUpdater/.gitignore @@ -0,0 +1,3 @@ +bin +build +tool/SDKUpdater.jar diff --git a/processing/mode/tools/SDKUpdater/.project b/processing/mode/tools/SDKUpdater/.project new file mode 100644 index 000000000..730c03fa8 --- /dev/null +++ b/processing/mode/tools/SDKUpdater/.project @@ -0,0 +1,34 @@ + + + android-mode-sdkupdater + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1675640664206 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/processing/mode/tools/SDKUpdater/.settings/org.eclipse.buildship.core.prefs b/processing/mode/tools/SDKUpdater/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 000000000..e8895216f --- /dev/null +++ b/processing/mode/tools/SDKUpdater/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir= +eclipse.preferences.version=1 diff --git a/processing/mode/tools/SDKUpdater/.settings/org.eclipse.jdt.core.prefs b/processing/mode/tools/SDKUpdater/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..6558ab78f --- /dev/null +++ b/processing/mode/tools/SDKUpdater/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=17 diff --git a/processing/mode/tools/SDKUpdater/build.gradle b/processing/mode/tools/SDKUpdater/build.gradle new file mode 100644 index 000000000..a47fffbb1 --- /dev/null +++ b/processing/mode/tools/SDKUpdater/build.gradle @@ -0,0 +1,62 @@ +import java.nio.file.Files +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +configurations { + implementation.extendsFrom implementationCopy +} + +dependencies { + // implementation group: "org.processing", name: "pde", version: "${processingVersion}" + // implementation group: "org.processing", name: "java-mode", version: "${processingVersion}" + implementation fileTree(include: ["AndroidMode.jar", "pde.jar", "JavaMode.jar"], dir: '../../mode') + + implementationCopy group: "com.android.tools", name: "sdklib", version: "${toolsLibVersion}" + implementationCopy group: "com.android.tools", name: "repository", version: "${toolsLibVersion}" + implementationCopy group: "com.android.tools", name: "common", version: "${toolsLibVersion}" +} + +// This task copies the gradle tooling jar into the mode folder +task copyToLib(type: Copy) { + from configurations.implementationCopy.files { + + include '**/common-*jar' + include '**/commons-compress-*jar' + include '**/guava-*jar' + include '**/httpcore-*jar' + include '**/istack-*jar' + include '**/jakarta.activation-api-*jar' + include '**/jakarta.xml.bind-api-*jar' + include '**/jaxb-runtime-*jar' + include '**/kotlin-stdlib-1*jar' + include '**/shared-*jar' + include '**/protos-*jar' + include '**/protob*jar' + include '**/sdklib-*jar' + include '**/repository-*jar' + + } + into "lib" +} +build.dependsOn(copyToLib) +compileJava.dependsOn(':mode:copyToLib') + +sourceSets { + main { + java { + srcDirs = ["src/"] + } + } +} + +clean.doFirst { + delete "tool" + delete "lib" +} + +build.doLast { + // Copy jar file to tool folder + File toolJar = file("tool/SDKUpdater.jar"); + toolJar.mkdirs(); + Files.copy(file("$buildDir/libs/SDKUpdater.jar").toPath(), + toolJar.toPath(), REPLACE_EXISTING); +} diff --git a/processing/mode/tools/SDKUpdater/src/processing/mode/android/tools/SDKUpdater.java b/processing/mode/tools/SDKUpdater/src/processing/mode/android/tools/SDKUpdater.java new file mode 100644 index 000000000..685b6716c --- /dev/null +++ b/processing/mode/tools/SDKUpdater/src/processing/mode/android/tools/SDKUpdater.java @@ -0,0 +1,654 @@ +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Part of the Processing project - http://processing.org + + Copyright (c) 2017 The Processing Foundation + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 + as published by the Free Software Foundation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package processing.mode.android.tools; + +import com.android.repository.api.*; +import com.android.repository.impl.meta.RepositoryPackages; +import com.android.repository.util.InstallerUtil; +import com.android.sdklib.repository.AndroidSdkHandler; +import com.android.sdklib.repository.installer.SdkInstallerUtil; +import com.android.sdklib.repository.legacy.LegacyDownloader; +import com.android.sdklib.tool.sdkmanager.SdkManagerCli; +import com.android.prefs.AndroidLocationsSingleton; + +import processing.app.Base; +import processing.app.Preferences; +import processing.app.tools.Tool; +import processing.app.ui.Toolkit; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import javax.swing.table.DefaultTableModel; + +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Vector; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; + +@SuppressWarnings("serial") +public class SDKUpdater extends JFrame implements PropertyChangeListener, Tool { + final static private int NUM_ROWS = 10; + final static private int COL_WIDTH = Toolkit.zoom(220); + + final static private int BORDER = Toolkit.zoom(13); + final static private int GAP = Toolkit.zoom(13); + final static private int INSET = Toolkit.zoom(1); + final static private int BUTTON_WIDTH = Toolkit.zoom(85); + final static private int BUTTON_HEIGHT = Toolkit.zoom(25); + + // private final Vector columns = new Vector<>(Arrays.asList( +// AndroidMode.getTextString("sdk_updater.name_column"), +// AndroidMode.getTextString("sdk_updater.version_column"), +// AndroidMode.getTextString("sdk_updater.available_column"))); + private final Vector columns = new Vector<>(Arrays.asList( + "Package name", "Installed version", "Available update" )); + + private static final String PROPERTY_CHANGE_QUERY = "query"; + + private File sdkFolder; + + private QueryTask queryTask; + private DownloadTask downloadTask; + private boolean downloadTaskRunning; + + private Vector> packageList; + private DefaultTableModel packageTable; + private int numUpdates; + + private JProgressBar progressBar; + private JLabel status; + private JLabel statusSecondary; + private JButton actionButton; + private JTable table; + + + @Override + public void init(Base base) { + createLayout(base.getActiveEditor() == null); + } + + + @Override + public void run() { + setVisible(true); + String path = Preferences.get("android.sdk.path"); + sdkFolder = new File(path); + queryTask = new QueryTask(); + queryTask.addPropertyChangeListener(this); + queryTask.execute(); +// status.setText(AndroidMode.getTextString("sdk_updater.query_message")); + status.setText("Querying packages..."); + statusSecondary.setText(""); + } + + + @Override + public String getMenuTitle() { +// return AndroidMode.getTextString("menu.android.sdk_updater"); + return "SDK Updater"; + } + + + @Override + public void propertyChange(PropertyChangeEvent evt) { + switch (evt.getPropertyName()) { + case PROPERTY_CHANGE_QUERY: + progressBar.setIndeterminate(false); + if (numUpdates == 0) { + actionButton.setEnabled(false); +// status.setText(AndroidMode.getTextString("sdk_updater.no_updates_message")); + status.setText("No updates available"); + statusSecondary.setText(""); + } else { + actionButton.setEnabled(true); + if (numUpdates == 1) { +// status.setText(AndroidMode.getTextString("sdk_updater.one_updates_message")); + status.setText("1 update found!"); + statusSecondary.setText(""); + } else { +// status.setText(AndroidMode.getTextString("sdk_updater.many_updates_message", numUpdates)); + status.setText(numUpdates + " updates found!"); + statusSecondary.setText(""); + } + } + break; + } + } + + class QueryTask extends SwingWorker { + ProgressIndicator progress; + + QueryTask() { + super(); + progress = new CustomProgressIndicatorToMonitor(); + } + + @Override + protected Object doInBackground() throws Exception { + numUpdates = 0; + packageList = new Vector<>(); + + /* Following code is from listPackages() of com.android.sdklib.tool.SdkManagerCli + with some changes + */ + AndroidSdkHandler mHandler = AndroidSdkHandler.getInstance(AndroidLocationsSingleton.INSTANCE, sdkFolder.toPath()); + + RepoManager mRepoManager = mHandler.getSdkManager(progress); + mRepoManager.loadSynchronously(0, progress, new LegacyDownloader(new SettingsController() { + @Override + public boolean getForceHttp() { + return false; + } + + @Override + public void setForceHttp(boolean b) { } + + @Override + public Channel getChannel() { + return null; + } + + @Override + public boolean getDisableSdkPatches() { + return false; + } + + @Override + public void setDisableSdkPatches(boolean arg0) { + } + }), null); + + RepositoryPackages packages = mRepoManager.getPackages(); + HashMap> installed = new HashMap>(); + for (LocalPackage local : packages.getLocalPackages().values()) { + String path = local.getPath(); + String name = local.getDisplayName(); + String ver = local.getVersion().toString(); + // Remove version from the display name + int rev = name.indexOf(", rev"); + if (-1 < rev) { + name = name.substring(0, rev); + } + int maj = ver.indexOf("."); + if (-1 < maj) { + String major = ver.substring(0, maj); + int pos = name.indexOf(major); + if (-1 < pos) { + name = name.substring(0, pos).trim(); + } + } + installed.put(path, Arrays.asList(name, ver)); + } + + HashMap> updated = new HashMap>(); + for (UpdatablePackage update : packages.getUpdatedPkgs()) { + String path = update.getPath(); + String loc = update.getLocal().getVersion().toString(); + String rem = update.getRemote().getVersion().toString(); + updated.put(path, Arrays.asList(loc, rem)); + } + + for (String path: installed.keySet()) { + Vector info = new Vector<>(); + List locInfo = installed.get(path); + info.add(locInfo.get(0)); + info.add(locInfo.get(1)); + if (updated.containsKey(path)) { + String upVer = updated.get(path).get(1); + info.add(upVer); + numUpdates++; + } else { + info.add(""); + } + packageList.add(info); + } + + return null; + } + + @Override + protected void done() { + super.done(); + + try { + get(); + firePropertyChange(PROPERTY_CHANGE_QUERY, "query", "SUCCESS"); + + if (packageList != null) { + packageTable.setDataVector(packageList, columns); + packageTable.fireTableDataChanged(); + } + } catch (InterruptedException | CancellationException e) { + this.cancel(false); + } catch (ExecutionException e) { + this.cancel(true); + JOptionPane.showMessageDialog(null, + e.getCause().toString(), "Error", JOptionPane.ERROR_MESSAGE); + e.printStackTrace(); + } + } + } + + class DownloadTask extends SwingWorker { + ProgressIndicator progress; + + DownloadTask() { + super(); + progress = new CustomProgressIndicatorToMonitor(); + } + + @Override + protected Object doInBackground() throws Exception { + downloadTaskRunning = true; + + /* Following code is from installPackages() of com.android.sdklib.tool.SdkManagerCli + with some changes + */ + AndroidSdkHandler mHandler = AndroidSdkHandler.getInstance(AndroidLocationsSingleton.INSTANCE, sdkFolder.toPath()); + + CustomSettings settings = new CustomSettings(); + Downloader downloader = new LegacyDownloader(settings); + + RepoManager mRepoManager = mHandler.getSdkManager(progress); + mRepoManager.loadSynchronously(0, progress, downloader, settings); + + List remotes = new ArrayList<>(); + for (String path : settings.getPaths(mRepoManager)) { + RemotePackage p = mRepoManager.getPackages().getRemotePackages().get(path); + if (p == null) { +// progress.logWarning(AndroidMode.getTextString("sdk_updater.warning_failed_finding_package", path)); + progress.logWarning("Failed to find package " + path); + throw new SdkManagerCli.CommandFailedException(); + } + remotes.add(p); + } + remotes = InstallerUtil.computeRequiredPackages( + remotes, mRepoManager.getPackages(), progress); + if (remotes != null) { + for (RemotePackage p : remotes) { + Installer installer = SdkInstallerUtil.findBestInstallerFactory(p, mHandler) + .createInstaller(p, mRepoManager, downloader); + if (!(installer.prepare(progress) && installer.complete(progress))) { + // there was an error, abort. + throw new SdkManagerCli.CommandFailedException(); + } + } + } else { +// progress.logWarning(AndroidMode.getTextString("sdk_updater.warning_failed_computing_dependency_list")); + progress.logWarning("Unable to compute a complete list of dependencies."); + throw new SdkManagerCli.CommandFailedException(); + } + + return null; + } + + @Override + protected void done() { + super.done(); + + try { + get(); + actionButton.setEnabled(false); +// status.setText(AndroidMode.getTextString("sdk_updater.refresh_package_message")); + status.setText("Refreshing packages..."); + statusSecondary.setText(""); + queryTask = new QueryTask(); + queryTask.addPropertyChangeListener(SDKUpdater.this); + queryTask.execute(); + } catch (InterruptedException | CancellationException e) { + this.cancel(true); + } catch (ExecutionException e) { + this.cancel(true); + JOptionPane.showMessageDialog(null, + e.getCause().toString(), "Error", JOptionPane.ERROR_MESSAGE); + e.printStackTrace(); + } finally { + downloadTaskRunning = false; + progressBar.setIndeterminate(false); + } + } + + class CustomSettings implements SettingsController { + /* Dummy implementation with some necessary methods from the original + implementation in com.android.sdklib.tool.SdkManagerCli + */ + @Override + public boolean getForceHttp() { + return false; + } + + @Override + public void setForceHttp(boolean b) { } + + @Override + public Channel getChannel() { + return null; + } + + public java.util.List getPaths(RepoManager mgr) { + List updates = new ArrayList<>(); + for(UpdatablePackage upd : mgr.getPackages().getUpdatedPkgs()) { + if (!upd.getRemote().obsolete()) { + updates.add(upd.getRepresentative().getPath()); + } + } + return updates; + } + + @Override + public boolean getDisableSdkPatches() { + return false; + } + + @Override + public void setDisableSdkPatches(boolean arg0) { + // TODO Auto-generated method stub + + } + } + } + + private void createLayout(final boolean standalone) { + setTitle(getMenuTitle()); + + Container outer = getContentPane(); + outer.removeAll(); + + Box verticalBox = Box.createVerticalBox(); + verticalBox.setBorder(new EmptyBorder(BORDER, BORDER, BORDER, BORDER)); + outer.add(verticalBox); + + /* Packages panel */ + JPanel packagesPanel = new JPanel(); + + BoxLayout boxLayout = new BoxLayout(packagesPanel, BoxLayout.Y_AXIS); + packagesPanel.setLayout(boxLayout); + + // Packages table + packageTable = new DefaultTableModel(NUM_ROWS, columns.size()) { + @Override + public boolean isCellEditable(int row, int column) { + return false; + } + + @Override + public Class getColumnClass(int columnIndex) { + return String.class; + } + }; + + table = new JTable(packageTable) { + @Override + public String getColumnName(int column) { + return columns.get(column); + } + }; + table.setFillsViewportHeight(true); + table.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS); + table.setRowHeight(Toolkit.zoom(table.getRowHeight())); + Dimension dim = new Dimension(table.getColumnCount() * COL_WIDTH, + table.getRowHeight() * NUM_ROWS); + table.setPreferredScrollableViewportSize(dim); + + packagesPanel.add(new JScrollPane(table)); + + JPanel controlPanel = new JPanel(); + GridBagLayout gridBagLayout = new GridBagLayout(); + controlPanel.setLayout(gridBagLayout); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.insets = new Insets(INSET, INSET, INSET, INSET); + + status = new JLabel(); + status.setText("Starting up..."); + gbc.gridx = 0; + gbc.gridy = 0; + controlPanel.add(status, gbc); + + statusSecondary = new JLabel(); + statusSecondary.setText("Getting Detailed Information Here..."); + statusSecondary.setFont(new Font("Calibri", Font.PLAIN, 12)); + gbc.gridx = 0; + gbc.gridy = 1; + controlPanel.add(statusSecondary, gbc); + + // Using an indeterminate progress bar from now until we learn + // how to update the fraction of the query/download process: + // https://github.com/processing/processing-android/issues/362 + progressBar = new JProgressBar(); + gbc.gridx = 0; + gbc.gridy = 2; + gbc.weightx = 1.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + controlPanel.add(progressBar, gbc); + + actionButton = new JButton("Update"); // handles Update/Cancel + actionButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if (downloadTaskRunning) { // i.e button state is Cancel + cancelTasks(); + } else { // i.e button state is Update + downloadTask = new DownloadTask(); + downloadTask.execute(); + + +// status.setText(AndroidMode.getTextString("sdk_updater.download_package_message")); + status.setText("Downloading available updates..."); + statusSecondary.setText(""); +// actionButton.setText(AndroidMode.getTextString("sdk_updater.cancel_button_label")); + actionButton.setText("Cancel"); + } + } + }); + actionButton.setEnabled(false); + actionButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT)); + gbc.gridx = 1; + gbc.gridy = 0; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + controlPanel.add(actionButton, gbc); + + ActionListener disposer = new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + cancelTasks(); + if (standalone) { + System.exit(0); + } else { + setVisible(false); + } + } + }; + +// JButton closeButton = new JButton(AndroidMode.getTextString("sdk_updater.close_button_label")); + JButton closeButton = new JButton("Close"); + closeButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT)); + closeButton.addActionListener(disposer); + closeButton.setEnabled(true); + gbc.gridx = 1; + gbc.gridy = 1; + gbc.weightx = 0.0; + gbc.fill = GridBagConstraints.HORIZONTAL; + controlPanel.add(closeButton, gbc); + + verticalBox.add(packagesPanel); + verticalBox.add(Box.createVerticalStrut(GAP)); + verticalBox.add(controlPanel); + pack(); + + JRootPane root = getRootPane(); + root.setDefaultButton(closeButton); + processing.app.ui.Toolkit.registerWindowCloseKeys(root, disposer); + processing.app.ui.Toolkit.setIcon(this); + + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + cancelTasks(); + super.windowClosing(e); + } + }); + + registerWindowCloseKeys(getRootPane(), disposer); + + setLocationRelativeTo(null); + setResizable(false); + setVisible(false); + } + + public void cancelTasks() { + queryTask.cancel(true); + if (downloadTaskRunning) { + downloadTask.cancel(true); +// status.setText(AndroidMode.getTextString("sdk_updater.download_canceled_message")); + status.setText("Download canceled"); + statusSecondary.setText(""); + JOptionPane.showMessageDialog(null, +// AndroidMode.getTextString("sdk_updater.download_canceled_message"), + "Download canceled", + "Warning", JOptionPane.WARNING_MESSAGE); +// actionButton.setText(AndroidMode.getTextString("sdk_updater.update_button_label")); + actionButton.setText("Update"); + } + } + + + /** + * Registers key events for a Ctrl-W and ESC with an ActionListener + * that will take care of disposing the window. + */ + static public void registerWindowCloseKeys(JRootPane root, + ActionListener disposer) { + KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + root.registerKeyboardAction(disposer, stroke, + JComponent.WHEN_IN_FOCUSED_WINDOW); + + int modifiers = java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + stroke = KeyStroke.getKeyStroke('W', modifiers); + root.registerKeyboardAction(disposer, stroke, + JComponent.WHEN_IN_FOCUSED_WINDOW); + } + + class CustomProgressIndicatorToMonitor implements com.android.repository.api.ProgressIndicator{ + + int percentage = 0; + String progressTextData = ""; + String progressTextDataDetailed = ""; + + @Override + public void setText(String progressText) { + progressTextData = progressText; + } + + @Override + public boolean isCanceled() { + return false; + } + + @Override + public void cancel() { + + } + + @Override + public void setCancellable(boolean cancellable) { + + } + + @Override + public boolean isCancellable() { + return false; + } + + @Override + public void setIndeterminate(boolean indeterminate) { + + } + + @Override + public boolean isIndeterminate() { + return false; + } + + @Override + public void setFraction(double progress) { + if(progress<=1 && progress>=0) { + percentage = ((int)(progress*100)); + // System.out.println("CustomProgressIndicatorToMonitor:Progress Bar Percentage:"+percentage); + progressBar.setValue(percentage); + status.setText(progressTextData+" "+percentage+" % "); + statusSecondary.setText(progressTextDataDetailed); + } + } + + @Override + public double getFraction() { + return 0; + } + + @Override + public void setSecondaryText(String s) { + progressTextDataDetailed = s; + // System.out.println("CustomProgressIndicatorToMonitor:SecondaryText:"+s); + } + + @Override + public void logWarning(String s) { + progressTextDataDetailed = s; + // System.out.println("CustomProgressIndicatorToMonitor:WarningText:"+s); + } + + @Override + public void logWarning(String s, Throwable e) { + + } + + @Override + public void logError(String s) { + progressTextDataDetailed = s; + // System.out.println("CustomProgressIndicatorToMonitor:ErrorText:"+s); + } + + @Override + public void logError(String s, Throwable e) { + + } + + @Override + public void logInfo(String s) { + progressTextDataDetailed = s; + // System.out.println("CustomProgressIndicatorToMonitor:LogInfoText:"+s); + } + } + +} diff --git a/processing/mode/version.properties b/processing/mode/version.properties new file mode 100644 index 000000000..fbf85d18d --- /dev/null +++ b/processing/mode/version.properties @@ -0,0 +1,29 @@ +# Basics +android-platform = 33 +android-platform-wear = 30 +android-platform-wear-arm = 24 +android-toolslib = 30.3.0 +android-gradle-plugin = 7.1.0 +gradle-wrapper = 7.4.2 + +# Minimum SDK versions for each type of project +android-min-app = 17 +android-min-wallpaper = 17 +android-min-vr = 19 +android-min-ar = 24 +android-min-wear = 25 + +# Dependencies. Latest versions could be found at: +# https://mvnrepository.com +# https://repo.gradle.org +# The format below is group%artifact +androidx.appcompat%appcompat = 1.6.0 +androidx.legacy%legacy-support-v4 = 1.0.0 +com.google.android.support%wearable = 2.9.0 +com.google.android.gms%play-services-wearable = 18.0.0 +com.google.vr = 1.180.0 +com.google.ar = 1.37.0 +org.processing = 4.0.0b7 +org.gradle%gradle-tooling-api = 7.2 +org.slf4j = 1.7.30 +org.eclipse.jdt = 3.19.300 diff --git a/processing/scripts/publish-module.gradle b/processing/scripts/publish-module.gradle new file mode 100644 index 000000000..5d3fdcb89 --- /dev/null +++ b/processing/scripts/publish-module.gradle @@ -0,0 +1,55 @@ +apply plugin: 'maven-publish' +apply plugin: 'signing' + +afterEvaluate { + publishing { + publications { + release(MavenPublication) { + artifact(libJar) + artifact(libSrc) + artifact(libMd5) + pom { + groupId = "org.p5android" + artifactId = "${libName}" + version = "${libVersion}" + packaging = "jar" + licenses { + license { + name = "GNU Lesser General Public License, version 2.1" + url = "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt" + distribution = "repo" + } + } + scm { + connection = 'scm:git:github.com/processing/processing-android.git' + developerConnection = 'scm:git:ssh://github.com/processing/processing-android.git' + url = 'https://github.com/processing/processing-android/tree/master' + } + } + pom.withXml { + def dependenciesNode = asNode().appendNode('dependencies') + libDependencies.each { + def dependencyNode = dependenciesNode.appendNode('dependency') + if (it.name == 'android') { + dependencyNode.appendNode('artifactId', 'android') + dependencyNode.appendNode('scope', 'runtime') + } else { + dependencyNode.appendNode('groupId', it.group) + dependencyNode.appendNode('artifactId', it.name) + dependencyNode.appendNode('version', it.version) + dependencyNode.appendNode('scope', 'implementation') + } + } + } + } + } + } +} + +ext["signing.keyId"] = rootProject.ext["signing.keyId"] +ext["signing.password"] = rootProject.ext["signing.password"] +ext["signing.secretKeyRingFile"] = rootProject.ext["signing.secretKeyRingFile"] + +signing { + sign publishing.publications +} \ No newline at end of file diff --git a/processing/scripts/publish-root.gradle b/processing/scripts/publish-root.gradle new file mode 100644 index 000000000..166c3bace --- /dev/null +++ b/processing/scripts/publish-root.gradle @@ -0,0 +1,36 @@ +// Create variables with empty default values +ext["signing.keyId"] = '' +ext["signing.password"] = '' +ext["signing.secretKeyRingFile"] = '' +ext["ossrhUsername"] = '' +ext["ossrhPassword"] = '' +ext["sonatypeStagingProfileId"] = '' + +File secretPropsFile = project.rootProject.file('local.properties') +if (secretPropsFile.exists()) { + // Read local.properties file first if it exists + Properties p = new Properties() + new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } + p.each { name, value -> ext[name] = value } +} else { + // Use system environment variables + ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') + ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') + ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') + ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') + ext["signing.password"] = System.getenv('SIGNING_PASSWORD') + ext["signing.secretKeyRingFile"] = System.getenv('SIGNING_SECRET_KEY_RING_FILE') +} + +// Set up Sonatype repository +nexusPublishing { + repositories { + sonatype { + stagingProfileId = sonatypeStagingProfileId + username = ossrhUsername + password = ossrhPassword + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + } + } +} \ No newline at end of file diff --git a/processing/settings.gradle b/processing/settings.gradle new file mode 100644 index 000000000..9e3fad4ab --- /dev/null +++ b/processing/settings.gradle @@ -0,0 +1,2 @@ +include ':mode', ':core', ':mode:libraries:vr', ':mode:libraries:ar', 'mode:tools:SDKUpdater' + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..193a03378 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':libs:processing-core', 'libs:google-vr',':libs:processing-vr', ':libs:processing-ar', ':apps:simple', ':apps:wallpaper', ':apps:arscene', ':apps:watchface', ':apps:fast2d', ':apps:armarkers' \ No newline at end of file diff --git a/src/processing/mode/android/AVD.java b/src/processing/mode/android/AVD.java deleted file mode 100644 index 92dad4cab..000000000 --- a/src/processing/mode/android/AVD.java +++ /dev/null @@ -1,197 +0,0 @@ -package processing.mode.android; - -import processing.app.Base; -import processing.app.exec.ProcessHelper; -import processing.app.exec.ProcessResult; -import processing.core.PApplet; - -import java.io.IOException; -import java.util.ArrayList; - - -public class AVD { - static private final String AVD_CREATE_PRIMARY = - "An error occurred while running “android create avd”"; - - static private final String AVD_CREATE_SECONDARY = - "The default Android emulator could not be set up. Make sure
    " + - "that the Android SDK is installed properly, and that the
    " + - "Android and Google APIs are installed for level " + AndroidBuild.sdkVersion + ".
    " + - "(Between you and me, occasionally, this error is a red herring,
    " + - "and your sketch may be launching shortly.)"; - - static private final String AVD_LOAD_PRIMARY = - "There is an error with the Processing AVD."; - static private final String AVD_LOAD_SECONDARY = - "This could mean that the Android tools need to be updated,
    " + - "or that the Processing AVD should be deleted (it will
    " + - "automatically re-created the next time you run Processing).
    " + - "Open the Android SDK Manager (underneath the Android menu)
    " + - "to check for any errors."; - - static private final String AVD_TARGET_PRIMARY = - "The Google APIs are not installed properly"; - static private final String AVD_TARGET_SECONDARY = - "Please re-read the installation instructions for Processing
    " + - "found at http://android.processing.org and try again."; - - static final String DEFAULT_SKIN = "WVGA800"; - static final String DEFAULT_SDCARD_SIZE = "64M"; - - /** Name of this avd. */ - protected String name; - - /** "android-7" or "Google Inc.:Google APIs:7" */ - protected String target; - - /** Default virtual device used by Processing. */ - static public final AVD defaultAVD = - new AVD("Processing-0" + Base.getRevision(), - "android-" + AndroidBuild.sdkVersion); -// "Google Inc.:Google APIs:" + AndroidBuild.sdkVersion); - - static ArrayList avdList; - static ArrayList badList; -// static ArrayList skinList; - - - public AVD(final String name, final String target) { - this.name = name; - this.target = target; - } - - - static protected void list(final AndroidSDK sdk) throws IOException { - try { - avdList = new ArrayList(); - badList = new ArrayList(); - ProcessResult listResult = - new ProcessHelper(sdk.getAndroidToolPath(), "list", "avds").execute(); - if (listResult.succeeded()) { - boolean badness = false; - for (String line : listResult) { - String[] m = PApplet.match(line, "\\s+Name\\:\\s+(\\S+)"); - if (m != null) { - if (!badness) { -// System.out.println("good: " + m[1]); - avdList.add(m[1]); - } else { -// System.out.println("bad: " + m[1]); - badList.add(m[1]); - } -// } else { -// System.out.println("nope: " + line); - } - // "The following Android Virtual Devices could not be loaded:" - if (line.contains("could not be loaded:")) { -// System.out.println("starting the bad list"); -// System.err.println("Could not list AVDs:"); -// System.err.println(listResult); - badness = true; -// break; - } - } - } else { - System.err.println("Unhappy inside exists()"); - System.err.println(listResult); - } - } catch (final InterruptedException ie) { } - } - - - protected boolean exists(final AndroidSDK sdk) throws IOException { - if (avdList == null) { - list(sdk); - } - for (String avd : avdList) { - if (Base.DEBUG) { - System.out.println("AVD.exists() checking for " + name + " against " + avd); - } - if (avd.equals(name)) { - return true; - } - } - return false; - } - - - /** - * Return true if a member of the renowned and prestigious - * "The following Android Virtual Devices could not be loaded:" club. - * (Prestigious may also not be the right word.) - */ - protected boolean badness() { - for (String avd : badList) { - if (avd.equals(name)) { - return true; - } - } - return false; - } - - - protected boolean create(final AndroidSDK sdk) throws IOException { - final String[] params = { - sdk.getAndroidToolPath(), - "create", "avd", - "-n", name, - "-t", target, - "-c", DEFAULT_SDCARD_SIZE, - "-s", DEFAULT_SKIN, - "--abi", "armeabi" - }; - - // Set the list to null so that exists() will check again - avdList = null; - - final ProcessHelper p = new ProcessHelper(params); - try { - // Passes 'no' to "Do you wish to create a custom hardware profile [no]" -// System.out.println("CREATE AVD STARTING"); - final ProcessResult createAvdResult = p.execute("no"); -// System.out.println("CREATE AVD HAS COMPLETED"); - if (createAvdResult.succeeded()) { - return true; - } - if (createAvdResult.toString().contains("Target id is not valid")) { - // They didn't install the Google APIs - Base.showWarningTiered("Android Error", AVD_TARGET_PRIMARY, AVD_TARGET_SECONDARY, null); -// throw new IOException("Missing required SDK components"); - } else { - // Just generally not working -// Base.showWarning("Android Error", AVD_CREATE_ERROR, null); - Base.showWarningTiered("Android Error", AVD_CREATE_PRIMARY, AVD_CREATE_SECONDARY, null); - System.out.println(createAvdResult); -// throw new IOException("Error creating the AVD"); - } - //System.err.println(createAvdResult); - } catch (final InterruptedException ie) { } - - return false; - } - - - static public boolean ensureProperAVD(final AndroidSDK sdk) { - try { - if (defaultAVD.exists(sdk)) { -// System.out.println("the avd exists"); - return true; - } -// if (badList.contains(defaultAVD)) { - if (defaultAVD.badness()) { -// Base.showWarning("Android Error", AVD_CANNOT_LOAD, null); - Base.showWarningTiered("Android Error", AVD_LOAD_PRIMARY, AVD_LOAD_SECONDARY, null); - return false; - } - if (defaultAVD.create(sdk)) { -// System.out.println("the avd was created"); - return true; - } - } catch (final Exception e) { -// Base.showWarning("Android Error", AVD_CREATE_ERROR, e); - Base.showWarningTiered("Android Error", AVD_CREATE_PRIMARY, AVD_CREATE_SECONDARY, null); - } - System.out.println("at bottom of ensure proper"); - return false; - } -} diff --git a/src/processing/mode/android/AndroidBuild.java b/src/processing/mode/android/AndroidBuild.java deleted file mode 100644 index b00cafea7..000000000 --- a/src/processing/mode/android/AndroidBuild.java +++ /dev/null @@ -1,1028 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2009-11 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.mode.android; - -import org.apache.tools.ant.BuildException; -import org.apache.tools.ant.DefaultLogger; -import org.apache.tools.ant.Project; -import org.apache.tools.ant.ProjectHelper; - -import processing.app.Base; -import processing.app.Library; -import processing.app.Preferences; -import processing.app.Sketch; -import processing.app.SketchException; -import processing.app.exec.ProcessHelper; -import processing.app.exec.ProcessResult; -import processing.core.PApplet; -import processing.mode.android.signing.JarSigner; -import processing.mode.java.JavaBuild; - -import java.io.*; -import java.security.Permission; - - -class AndroidBuild extends JavaBuild { - // static final String basePackage = "changethispackage.beforesubmitting.tothemarket"; - static final String basePackage = "processing.test"; - static String sdkName = "2.3.3"; - static String sdkVersion = "10"; // Android 2.3.3 (Gingerbread) - static String sdkTarget = "android-" + sdkVersion; - - private final AndroidSDK sdk; - private final File coreZipFile; - - /** whether this is a "debug" or "release" build */ - private String target; - private Manifest manifest; - - /** temporary folder safely inside a 8.3-friendly folder */ - private File tmpFolder; - - /** build.xml file for this project */ - private File buildFile; - - - public AndroidBuild(final Sketch sketch, final AndroidMode mode) { - super(sketch); - - sdk = mode.getSDK(); - coreZipFile = mode.getCoreZipLocation(); - } - - public static void setSdkTarget(AndroidSDK.SDKTarget target, Sketch sketch) { - sdkName = target.name; - sdkVersion = Integer.toString(target.version); - sdkTarget = "android-" + sdkVersion; - - Manifest manifest = new Manifest(sketch); - manifest.setSdkTarget(sdkVersion); - - Preferences.set("android.sdk.version", sdkVersion); - } - - /** - * Build into temporary folders (needed for the Windows 8.3 bugs in the Android SDK). - * @param target "debug" or "release" - * @throws SketchException - * @throws IOException - */ - public File build(String target) throws IOException, SketchException { - this.target = target; - File folder = createProject(); - if (folder != null) { - if (!antBuild()) { - return null; - } - } - return folder; - } - - - /** - * Tell the PDE to not complain about android.* packages and others that are - * part of the OS library set as if they're missing. - */ - protected boolean ignorableImport(String pkg) { - if (pkg.startsWith("android.")) return true; - if (pkg.startsWith("java.")) return true; - if (pkg.startsWith("javax.")) return true; - if (pkg.startsWith("org.apache.http.")) return true; - if (pkg.startsWith("org.json.")) return true; - if (pkg.startsWith("org.w3c.dom.")) return true; - if (pkg.startsWith("org.xml.sax.")) return true; - - if (pkg.startsWith("processing.core.")) return true; - if (pkg.startsWith("processing.data.")) return true; - if (pkg.startsWith("processing.event.")) return true; - if (pkg.startsWith("processing.opengl.")) return true; - - return false; - } - - - /** - * Create an Android project folder, and run the preprocessor on the sketch. - * Populates the 'src' folder with Java code, and 'libs' folder with the - * libraries and code folder contents. Also copies data folder to 'assets'. - */ - public File createProject() throws IOException, SketchException { - tmpFolder = createTempBuildFolder(sketch); - - // Create the 'src' folder with the preprocessed code. -// final File srcFolder = new File(tmpFolder, "src"); - srcFolder = new File(tmpFolder, "src"); - // this folder isn't actually used, but it's used by the java preproc to - // figure out the classpath, so we have to set it to something -// binFolder = new File(tmpFolder, "bin"); - // use the src folder, since 'bin' might be used by the ant build - binFolder = srcFolder; - if (processing.app.Base.DEBUG) { - Base.openFolder(tmpFolder); - } - - manifest = new Manifest(sketch); - // grab code from current editing window (GUI only) -// prepareExport(null); - - // build the preproc and get to work - AndroidPreprocessor preproc = new AndroidPreprocessor(sketch, getPackageName()); -// if (!preproc.parseSketchSize()) { -// String[] sizeInfo = PdePreprocessor.parseSketchSize(sketch.getMainProgram()); -// if (sizeInfo == null) { -// throw new SketchException("Could not parse the size() command."); -// } - // On Android, this init will throw a SketchException if there's a problem with size() - preproc.initSketchSize(sketch.getMainProgram()); - preproc.initSketchSmooth(sketch.getMainProgram()); - sketchClassName = preprocess(srcFolder, manifest.getPackageName(), preproc, false); - if (sketchClassName != null) { - File tempManifest = new File(tmpFolder, "AndroidManifest.xml"); - manifest.writeBuild(tempManifest, sketchClassName, target.equals("debug")); - - writeAntProps(new File(tmpFolder, "ant.properties")); - buildFile = new File(tmpFolder, "build.xml"); - writeBuildXML(buildFile, sketch.getName()); - writeProjectProps(new File(tmpFolder, "project.properties")); - writeLocalProps(new File(tmpFolder, "local.properties")); - - final File resFolder = new File(tmpFolder, "res"); - writeRes(resFolder, sketchClassName); - - // new location for SDK Tools 17: /opt/android/tools/proguard/proguard-android.txt -// File proguardSrc = new File(sdk.getSdkFolder(), "tools/lib/proguard.cfg"); -// File proguardDst = new File(tmpFolder, "proguard.cfg"); -// Base.copyFile(proguardSrc, proguardDst); - - final File libsFolder = mkdirs(tmpFolder, "libs"); - final File assetsFolder = mkdirs(tmpFolder, "assets"); - -// InputStream input = PApplet.createInput(getCoreZipLocation()); -// PApplet.saveStream(new File(libsFolder, "processing-core.jar"), input); - Base.copyFile(coreZipFile, new File(libsFolder, "processing-core.jar")); - - // Copy any imported libraries (their libs and assets), - // and anything in the code folder contents to the project. - copyLibraries(libsFolder, assetsFolder); - copyCodeFolder(libsFolder); - - // Copy the data folder (if one exists) to the project's 'assets' folder - final File sketchDataFolder = sketch.getDataFolder(); - if (sketchDataFolder.exists()) { - Base.copyDir(sketchDataFolder, assetsFolder); - } - - // Do the same for the 'res' folder. - // http://code.google.com/p/processing/issues/detail?id=767 - final File sketchResFolder = new File(sketch.getFolder(), "res"); - if (sketchResFolder.exists()) { - Base.copyDir(sketchResFolder, resFolder); - } - } - return tmpFolder; - } - - - /** - * The Android dex util pukes on paths containing spaces, which will happen - * most of the time on Windows, since Processing sketches wind up in - * "My Documents". Therefore, build android in a temp file. - * http://code.google.com/p/android/issues/detail?id=4567 - * - * TODO: better would be to retrieve the 8.3 name for the sketch folder! - * - * @param sketch - * @return A folder in which to build the android sketch - * @throws IOException - */ - private File createTempBuildFolder(final Sketch sketch) throws IOException { - final File tmp = File.createTempFile("android", "sketch"); - if (!(tmp.delete() && tmp.mkdir())) { - throw new IOException("Cannot create temp dir " + tmp + " to build android sketch"); - } - return tmp; - } - - - protected File createExportFolder() throws IOException { -// Sketch sketch = editor.getSketch(); - // Create the 'android' build folder, and move any existing version out. - File androidFolder = new File(sketch.getFolder(), "android"); - if (androidFolder.exists()) { -// Date mod = new Date(androidFolder.lastModified()); - String stamp = AndroidMode.getDateStamp(androidFolder.lastModified()); - File dest = new File(sketch.getFolder(), "android." + stamp); - boolean result = androidFolder.renameTo(dest); - if (!result) { - ProcessHelper mv; - ProcessResult pr; - try { - System.err.println("createProject renameTo() failed, resorting to mv/move instead."); - mv = new ProcessHelper("mv", androidFolder.getAbsolutePath(), dest.getAbsolutePath()); - pr = mv.execute(); - -// } catch (IOException e) { -// editor.statusError(e); -// return null; -// - } catch (InterruptedException e) { - e.printStackTrace(); - return null; - } - if (!pr.succeeded()) { - System.err.println(pr.getStderr()); - Base.showWarning("Failed to rename", - "Could not rename the old “android” build folder.\n" + - "Please delete, close, or rename the folder\n" + - androidFolder.getAbsolutePath() + "\n" + - "and try again." , null); - Base.openFolder(sketch.getFolder()); - return null; - } - } - } else { - boolean result = androidFolder.mkdirs(); - if (!result) { - Base.showWarning("Folders, folders, folders", - "Could not create the necessary folders to build.\n" + - "Perhaps you have some file permissions to sort out?", null); - return null; - } - } - return androidFolder; - } - - - public File exportProject() throws IOException, SketchException { -// File projectFolder = build("debug"); -// if (projectFolder == null) { -// return null; -// } - // this will set debuggable to true in the .xml file - target = "debug"; - File projectFolder = createProject(); - if (projectFolder != null) { - File exportFolder = createExportFolder(); - Base.copyDir(projectFolder, exportFolder); - return exportFolder; - } - return null; - } - - public File exportPackage(String keyStorePassword) throws Exception { - File projectFolder = build("release"); - if (projectFolder == null) return null; - - File signedPackage = signPackage(projectFolder, keyStorePassword); - if (signedPackage == null) return null; - - File exportFolder = createExportFolder(); - Base.copyDir(projectFolder, exportFolder); - return new File(exportFolder, "/bin/"); - } - - private File signPackage(File projectFolder, String keyStorePassword) throws Exception { - File keyStore = AndroidKeyStore.getKeyStore(); - if (keyStore == null) return null; - - File unsignedPackage = new File(projectFolder, "bin/" + sketch.getName() + "-release-unsigned.apk"); - if (!unsignedPackage.exists()) return null; - File signedPackage = new File(projectFolder, "bin/" + sketch.getName() + "-release-signed.apk"); - - JarSigner.signJar(unsignedPackage, signedPackage, AndroidKeyStore.ALIAS_STRING, keyStorePassword, keyStore.getAbsolutePath(), keyStorePassword); - - //if (verifySignedPackage(unsignedPackage)) { - /*File signedPackage = new File(projectFolder, "bin/" + sketch.getName() + "-release-signed.apk"); - if (signedPackage.exists()) { - boolean deleteResult = signedPackage.delete(); - if (!deleteResult) { - Base.showWarning("Error during package signing", - "Unable to delete old signed package"); - return null; - } - } - - boolean renameResult = unsignedPackage.renameTo(signedPackage); - if (!renameResult) { - Base.showWarning("Error during package signing", - "Unable to rename package file"); - return null; - }*/ - - File alignedPackage = zipalignPackage(signedPackage, projectFolder); - return alignedPackage; - /*} else { - Base.showWarning("Error during package signing", - "Verification of the signed package has failed"); - return null; - }*/ - } - - /*private boolean verifySignedPackage(File signedPackage) throws Exception { - String[] args = { - "-verify", signedPackage.getCanonicalPath() - }; - - PrintStream defaultPrintStream = System.out; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - PrintStream printStream = new PrintStream(baos); - System.setOut(printStream); - - SystemExitControl.forbidSystemExitCall(); - try { - JarSigner.main(args); - } catch (SystemExitControl.ExitTrappedException ignored) { } - SystemExitControl.enableSystemExitCall(); - - System.setOut(defaultPrintStream); - String result = baos.toString(); - - baos.close(); - printStream.close(); - - return result.contains("verified"); - } */ - - private File zipalignPackage(File signedPackage, File projectFolder) throws IOException, InterruptedException { - - File buildToolsFolder = new File(sdk.getSdkFolder(), "build-tools").listFiles()[0]; - String zipalignPath = buildToolsFolder.getAbsolutePath() + "/zipalign"; - - File alignedPackage = new File(projectFolder, "bin/" + sketch.getName() + "-release-signed-aligned.apk"); - - String[] args = { - zipalignPath, "-v", "-f", "4", - signedPackage.getAbsolutePath(), alignedPackage.getAbsolutePath() - }; - - Process alignProcess = Runtime.getRuntime().exec(args); - alignProcess.waitFor(); - - if (alignedPackage.exists()) return alignedPackage; - return null; - } - - /* - // SDK tools 17 have a problem where 'dex' won't pick up the libs folder - // (which contains our friend processing-core.jar) unless your current - // working directory is the same as the build file. So this is an unpleasant - // workaround, at least until things are fixed or we hear of a better way. - // This was fixed in SDK 19 (and Processing revision 0205) so we've now - // disabled this portion of the code. - protected boolean antBuild_dexworkaround() throws SketchException { - try { -// ProcessHelper helper = new ProcessHelper(tmpFolder, new String[] { "ant", target }); - // Windows doesn't include full paths, so make 'em happen. - String cp = System.getProperty("java.class.path"); - String[] cpp = PApplet.split(cp, File.pathSeparatorChar); - for (int i = 0; i < cpp.length; i++) { - cpp[i] = new File(cpp[i]).getAbsolutePath(); - } - cp = PApplet.join(cpp, File.pathSeparator); - - // Since Ant may or may not be installed, call it from the .jar file, - // though hopefully 'java' is in the classpath.. Given what we do in - // processing.mode.java.runner (and it that it works), should be ok. - String[] cmd = new String[] { - "java", - "-cp", cp, //System.getProperty("java.class.path"), - "org.apache.tools.ant.Main", target -// "ant", target - }; - ProcessHelper helper = new ProcessHelper(tmpFolder, cmd); - ProcessResult pr = helper.execute(); - if (pr.getResult() != 0) { -// System.err.println("mo builds, mo problems"); - System.err.println(pr.getStderr()); - System.out.println(pr.getStdout()); - // the actual javac errors and whatnot go to stdout - antBuildProblems(pr.getStdout(), pr.getStderr()); - return false; - } - - } catch (InterruptedException e) { - return false; - - } catch (IOException e) { - e.printStackTrace(); - return false; - } - return true; - } - */ - - - /* - public class HopefullyTemporaryWorkaround extends org.apache.tools.ant.Main { - - protected void exit(int exitCode) { - // I want to exit, but let's not System.exit() - System.out.println("gonna exit"); - System.out.flush(); - System.err.flush(); - } - } - - - protected boolean antBuild() throws SketchException { - String[] cmd = new String[] { - "-main", "processing.mode.android.HopefullyTemporaryWorkaround", - "-Duser.dir=" + tmpFolder.getAbsolutePath(), - "-logfile", "/Users/fry/Desktop/ant-log.txt", - "-verbose", - "-help", -// "debug" - }; - HopefullyTemporaryWorkaround.main(cmd); - return true; -// ProcessResult listResult = -// new ProcessHelper("ant", "debug", tmpFolder).execute(); -// if (listResult.succeeded()) { -// boolean badness = false; -// for (String line : listResult) { -// } -// } - } - */ - - - protected boolean antBuild() throws SketchException { -// System.setProperty("user.dir", tmpFolder.getAbsolutePath()); // oh why not { because it doesn't help } - final Project p = new Project(); -// p.setBaseDir(tmpFolder); // doesn't seem to do anything - -// System.out.println(tmpFolder.getAbsolutePath()); -// p.setUserProperty("user.dir", tmpFolder.getAbsolutePath()); - String path = buildFile.getAbsolutePath().replace('\\', '/'); - p.setUserProperty("ant.file", path); - - // deals with a problem where javac error messages weren't coming through - //p.setUserProperty("build.compiler", "extJavac"); - // p.setUserProperty("build.compiler.emacs", "true"); // does nothing - - // try to spew something useful to the console - final DefaultLogger consoleLogger = new DefaultLogger(); - consoleLogger.setErrorPrintStream(System.err); - consoleLogger.setOutputPrintStream(System.out); // ? uncommented before - // WARN, INFO, VERBOSE, DEBUG - // consoleLogger.setMessageOutputLevel(Project.MSG_ERR); - consoleLogger.setMessageOutputLevel(Project.MSG_INFO); -// consoleLogger.setMessageOutputLevel(Project.MSG_DEBUG); - p.addBuildListener(consoleLogger); - - // This logger is used to pick up javac errors to be parsed into - // SketchException objects. Note that most errors seem to show up on stdout - // since that's where the [javac] prefixed lines are coming through. - final DefaultLogger errorLogger = new DefaultLogger(); - final ByteArrayOutputStream errb = new ByteArrayOutputStream(); - final PrintStream errp = new PrintStream(errb); - errorLogger.setErrorPrintStream(errp); - final ByteArrayOutputStream outb = new ByteArrayOutputStream(); - final PrintStream outp = new PrintStream(outb); - errorLogger.setOutputPrintStream(outp); - errorLogger.setMessageOutputLevel(Project.MSG_INFO); - // errorLogger.setMessageOutputLevel(Project.MSG_DEBUG); - p.addBuildListener(errorLogger); - - try { -// editor.statusNotice("Building sketch for Android..."); - p.fireBuildStarted(); - p.init(); - final ProjectHelper helper = ProjectHelper.getProjectHelper(); - p.addReference("ant.projectHelper", helper); - helper.parse(p, buildFile); - // p.executeTarget(p.getDefaultTarget()); - p.executeTarget(target); -// editor.statusNotice("Finished building sketch."); - return true; - - } catch (final BuildException e) { - // Send a "build finished" event to the build listeners for this project. - p.fireBuildFinished(e); - - // PApplet.println(new String(errb.toByteArray())); - // PApplet.println(new String(outb.toByteArray())); - - // String errorOutput = new String(errb.toByteArray()); - // String[] errorLines = - // errorOutput.split(System.getProperty("line.separator")); - // PApplet.println(errorLines); - - //final String outPile = new String(outb.toByteArray()); - //antBuildProblems(new String(outb.toByteArray()) - antBuildProblems(new String(outb.toByteArray()), - new String(errb.toByteArray())); - } - return false; - } - - - void antBuildProblems(String outPile, String errPile) throws SketchException { - final String[] outLines = - outPile.split(System.getProperty("line.separator")); - final String[] errLines = - errPile.split(System.getProperty("line.separator")); - - for (final String line : outLines) { - final String javacPrefix = "[javac]"; - final int javacIndex = line.indexOf(javacPrefix); - if (javacIndex != -1) { -// System.out.println("checking: " + line); -// final Sketch sketch = editor.getSketch(); - // String sketchPath = sketch.getFolder().getAbsolutePath(); - int offset = javacIndex + javacPrefix.length() + 1; - String[] pieces = - PApplet.match(line.substring(offset), "^(.+):([0-9]+):\\s+(.+)$"); - if (pieces != null) { -// PApplet.println(pieces); - String fileName = pieces[1]; - // remove the path from the front of the filename - //fileName = fileName.substring(fileName.lastIndexOf('/') + 1); - fileName = fileName.substring(fileName.lastIndexOf(File.separatorChar) + 1); - final int lineNumber = PApplet.parseInt(pieces[2]) - 1; -// PApplet.println("looking for " + fileName + " line " + lineNumber); - SketchException rex = placeException(pieces[3], fileName, lineNumber); - if (rex != null) { -// System.out.println("found a rex"); -// rex.hideStackTrace(); -// editor.statusError(rex); -// return false; // get outta here - throw rex; - } - } - } - } - - // Couldn't parse the exception, so send something generic - SketchException skex = - new SketchException("Error from inside the Android tools, " + - "check the console."); - - // Try to parse anything else we might know about - for (final String line : errLines) { - if (line.contains("Unable to resolve target '" + sdkTarget + "'")) { - System.err.println("Use the Android SDK Manager (under the Android"); - System.err.println("menu) to install the SDK platform and "); - System.err.println("Google APIs for Android " + sdkName + - " (API " + sdkVersion + ")"); - skex = new SketchException("Please install the SDK platform and " + - "Google APIs for API " + sdkVersion); - } - } - // Stack trace is not relevant, just the message. - skex.hideStackTrace(); - throw skex; - } - - - String getPathForAPK() { - String suffix = target.equals("release") ? "release-unsigned" : "debug"; - String apkName = "bin/" + sketch.getName() + "-" + suffix + ".apk"; - final File apkFile = new File(tmpFolder, apkName); - if (!apkFile.exists()) { - return null; - } - return apkFile.getAbsolutePath(); - } - - - private void writeAntProps(final File file) { - final PrintWriter writer = PApplet.createWriter(file); - writer.println("application-package=" + getPackageName()); - writer.flush(); - writer.close(); - } - - - private void writeBuildXML(final File file, final String projectName) { - final PrintWriter writer = PApplet.createWriter(file); - writer.println(""); - - writer.println(""); - - writer.println(" "); - writer.println(" "); - - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - - writer.println(" "); - writer.println(" "); - - writer.println(" "); - - writer.println(" "); - writer.println(" "); - -// Override target from maint android build file - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - - writer.println(" "); - writer.println(" "); - writer.println(" Instrumenting classes from ${out.absolute.dir}/classes..."); - - - writer.println(" "); - - - writer.println(" "); - - - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - - writer.println(" "); - writer.println(" "); - writer.println(" Creating library output jar file..."); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" Custom jar packaging exclusion: ${android.package.excludes}"); - writer.println(" "); - writer.println(" "); - - writer.println(" "); - - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - - writer.println(" "); - writer.println(" "); - - - - - - writer.println(" "); - - writer.println(" "); - - writer.println(" "); - - writer.println(" "); // should this be 'custom' instead of 1? - writer.println(" "); - - writer.println(""); - writer.flush(); - writer.close(); - } - - private void writeProjectProps(final File file) { - final PrintWriter writer = PApplet.createWriter(file); - writer.println("target=" + sdkTarget); - writer.println(); - // http://stackoverflow.com/questions/4821043/includeantruntime-was-not-set-for-android-ant-script - writer.println("# Suppress the javac task warnings about \"includeAntRuntime\""); - writer.println("build.sysclasspath=last"); - writer.flush(); - writer.close(); - } - - - private void writeLocalProps(final File file) { - final PrintWriter writer = PApplet.createWriter(file); - final String sdkPath = sdk.getSdkFolder().getAbsolutePath(); - if (Base.isWindows()) { - // Windows needs backslashes escaped, or it will also accept forward - // slashes in the build file. We're using the forward slashes since this - // path gets concatenated with a lot of others that use forwards anyway. - writer.println("sdk.dir=" + sdkPath.replace('\\', '/')); - } else { - writer.println("sdk.dir=" + sdkPath); - } - writer.flush(); - writer.close(); - } - - static final String ICON_96 = "icon-96.png"; - static final String ICON_72 = "icon-72.png"; - static final String ICON_48 = "icon-48.png"; - static final String ICON_36 = "icon-36.png"; - - private void writeRes(File resFolder, - String className) throws SketchException { - File layoutFolder = mkdirs(resFolder, "layout"); - File layoutFile = new File(layoutFolder, "main.xml"); - writeResLayoutMain(layoutFile); - - // write the icon files - File sketchFolder = sketch.getFolder(); - File localIcon36 = new File(sketchFolder, ICON_36); - File localIcon48 = new File(sketchFolder, ICON_48); - File localIcon72 = new File(sketchFolder, ICON_72); - File localIcon96 = new File(sketchFolder, ICON_96); - -// File drawableFolder = new File(resFolder, "drawable"); -// drawableFolder.mkdirs() - File buildIcon48 = new File(resFolder, "drawable/icon.png"); - File buildIcon36 = new File(resFolder, "drawable-ldpi/icon.png"); - File buildIcon72 = new File(resFolder, "drawable-hdpi/icon.png"); - File buildIcon96 = new File(resFolder, "drawable-xhdpi/icon.png"); - - if (!localIcon36.exists() && - !localIcon48.exists() && - !localIcon72.exists() && - !localIcon96.exists()) { - try { - // if no icons are in the sketch folder, then copy all the defaults - if (buildIcon36.getParentFile().mkdirs()) { - Base.copyFile(mode.getContentFile("icons/" + ICON_36), buildIcon36); - } else { - System.err.println("Could not create \"drawable-ldpi\" folder."); - } - if (buildIcon48.getParentFile().mkdirs()) { - Base.copyFile(mode.getContentFile("icons/" + ICON_48), buildIcon48); - } else { - System.err.println("Could not create \"drawable\" folder."); - } - if (buildIcon72.getParentFile().mkdirs()) { - Base.copyFile(mode.getContentFile("icons/" + ICON_72), buildIcon72); - } else { - System.err.println("Could not create \"drawable-hdpi\" folder."); - } - if (buildIcon96.getParentFile().mkdirs()) { - Base.copyFile(mode.getContentFile("icons/" + ICON_96), buildIcon96); - } else { - System.err.println("Could not create \"drawable-xhdpi\" folder."); - } - } catch (IOException e) { - e.printStackTrace(); - //throw new SketchException("Could not get Android icons"); - } - } else { - // if at least one of the icons already exists, then use that across the board - try { - if (localIcon36.exists()) { - if (new File(resFolder, "drawable-ldpi").mkdirs()) { - Base.copyFile(localIcon36, buildIcon36); - } - } - if (localIcon48.exists()) { - if (new File(resFolder, "drawable").mkdirs()) { - Base.copyFile(localIcon48, buildIcon48); - } - } - if (localIcon72.exists()) { - if (new File(resFolder, "drawable-hdpi").mkdirs()) { - Base.copyFile(localIcon72, buildIcon72); - } - } - if (localIcon96.exists()) { - if (new File(resFolder, "drawable-xhdpi").mkdirs()) { - Base.copyFile(localIcon96, buildIcon96); - } - } - } catch (IOException e) { - System.err.println("Problem while copying icons."); - e.printStackTrace(); - } - } - -// final File valuesFolder = mkdirs(resFolder, "values"); -// final File stringsFile = new File(valuesFolder, "strings.xml"); -// writeResValuesStrings(stringsFile, className); - } - - - private File mkdirs(final File parent, final String name) throws SketchException { - final File result = new File(parent, name); - if (!(result.exists() || result.mkdirs())) { - throw new SketchException("Could not create " + result); - } - return result; - } - - - private void writeResLayoutMain(final File file) { - final PrintWriter writer = PApplet.createWriter(file); - writer.println(""); - writer.println(""); - writer.println(""); - writer.flush(); - writer.close(); - } - - - // This recommended to be a string resource so that it can be localized. - // nah.. we're gonna be messing with it in the GUI anyway... - // people can edit themselves if they need to -// private static void writeResValuesStrings(final File file, -// final String className) { -// final PrintWriter writer = PApplet.createWriter(file); -// writer.println(""); -// writer.println(""); -// writer.println(" " + className + ""); -// writer.println(""); -// writer.flush(); -// writer.close(); -// } - - - /** - * For each library, copy .jar and .zip files to the 'libs' folder, - * and copy anything else to the 'assets' folder. - */ - private void copyLibraries(final File libsFolder, - final File assetsFolder) throws IOException { - for (Library library : getImportedLibraries()) { - // add each item from the library folder / export list to the output - for (File exportFile : library.getAndroidExports()) { - String exportName = exportFile.getName(); - if (!exportFile.exists()) { - System.err.println(exportFile.getName() + - " is mentioned in export.txt, but it's " + - "a big fat lie and does not exist."); - } else if (exportFile.isDirectory()) { - // Copy native library folders to the correct location - if (exportName.equals("armeabi") || - exportName.equals("armeabi-v7a") || - exportName.equals("x86")) { - Base.copyDir(exportFile, new File(libsFolder, exportName)); - } else { - // Copy any other directory to the assets folder - Base.copyDir(exportFile, new File(assetsFolder, exportName)); - } - } else if (exportName.toLowerCase().endsWith(".zip")) { - // As of r4 of the Android SDK, it looks like .zip files - // are ignored in the libs folder, so rename to .jar - System.err.println(".zip files are not allowed in Android libraries."); - System.err.println("Please rename " + exportFile.getName() + " to be a .jar file."); - String jarName = exportName.substring(0, exportName.length() - 4) + ".jar"; - Base.copyFile(exportFile, new File(libsFolder, jarName)); - - } else if (exportName.toLowerCase().endsWith(".jar")) { - Base.copyFile(exportFile, new File(libsFolder, exportName)); - - } else { - Base.copyFile(exportFile, new File(assetsFolder, exportName)); - } - } - } - } -// private void copyLibraries(final File libsFolder, -// final File assetsFolder) throws IOException { -// // Copy any libraries to the 'libs' folder -// for (Library library : getImportedLibraries()) { -// File libraryFolder = new File(library.getPath()); -// // in the list is a File object that points the -// // library sketch's "library" folder -// final File exportSettings = new File(libraryFolder, "export.txt"); -// final HashMap exportTable = -// Base.readSettings(exportSettings); -// final String androidList = exportTable.get("android"); -// String exportList[] = null; -// if (androidList != null) { -// exportList = PApplet.splitTokens(androidList, ", "); -// } else { -// exportList = libraryFolder.list(); -// } -// for (int i = 0; i < exportList.length; i++) { -// exportList[i] = PApplet.trim(exportList[i]); -// if (exportList[i].equals("") || exportList[i].equals(".") -// || exportList[i].equals("..")) { -// continue; -// } -// -// final File exportFile = new File(libraryFolder, exportList[i]); -// if (!exportFile.exists()) { -// System.err.println("File " + exportList[i] + " does not exist"); -// } else if (exportFile.isDirectory()) { -// System.err.println("Ignoring sub-folder \"" + exportList[i] + "\""); -// } else { -// final String name = exportFile.getName(); -// final String lcname = name.toLowerCase(); -// if (lcname.endsWith(".zip") || lcname.endsWith(".jar")) { -// // As of r4 of the Android SDK, it looks like .zip files -// // are ignored in the libs folder, so rename to .jar -// final String jarName = -// name.substring(0, name.length() - 4) + ".jar"; -// Base.copyFile(exportFile, new File(libsFolder, jarName)); -// } else { -// // just copy other files over directly -// Base.copyFile(exportFile, new File(assetsFolder, name)); -// } -// } -// } -// } -// } - - - private void copyCodeFolder(final File libsFolder) throws IOException { - // Copy files from the 'code' directory into the 'libs' folder - final File codeFolder = sketch.getCodeFolder(); - if (codeFolder != null && codeFolder.exists()) { - for (final File item : codeFolder.listFiles()) { - if (!item.isDirectory()) { - final String name = item.getName(); - final String lcname = name.toLowerCase(); - if (lcname.endsWith(".jar") || lcname.endsWith(".zip")) { - String jarName = name.substring(0, name.length() - 4) + ".jar"; - Base.copyFile(item, new File(libsFolder, jarName)); - } - } - } - } - } - - - protected String getPackageName() { - return manifest.getPackageName(); - } - - - public void cleanup() { - // don't want to be responsible for this - //rm(tempBuildFolder); - tmpFolder.deleteOnExit(); - } -} - -// http://www.avanderw.co.za/preventing-calls-to-system-exit-in-java/ -class SystemExitControl { - - @SuppressWarnings("serial") - public static class ExitTrappedException extends SecurityException { - } - - public static void forbidSystemExitCall() { - final SecurityManager securityManager = new SecurityManager() { - @Override - public void checkPermission(Permission permission) { - if (permission.getName().contains("exitVM")) { - throw new ExitTrappedException(); - } - } - }; - System.setSecurityManager(securityManager); - } - - public static void enableSystemExitCall() { - System.setSecurityManager(null); - } -} diff --git a/src/processing/mode/android/AndroidEditor.java b/src/processing/mode/android/AndroidEditor.java deleted file mode 100644 index 6f688f8eb..000000000 --- a/src/processing/mode/android/AndroidEditor.java +++ /dev/null @@ -1,612 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2009-11 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.mode.android; - -import processing.app.*; -import processing.core.PApplet; -import processing.mode.java.JavaEditor; - -import javax.swing.*; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.TimerTask; - -@SuppressWarnings("serial") -public class AndroidEditor extends JavaEditor { - private AndroidMode androidMode; - - class UpdateDeviceListTask extends TimerTask { - - private JMenu deviceMenu; - - public UpdateDeviceListTask(JMenu deviceMenu) { - this.deviceMenu = deviceMenu; - } - - @Override - public void run() { - if (androidMode.getSDK() == null) return; - - final Devices devices = Devices.getInstance(); - java.util.List deviceList = devices.findMultiple(false); - Device selectedDevice = devices.getSelectedDevice(); - - if (deviceList.size() == 0) { - //if (deviceMenu.getItem(0).isEnabled()) { - if (0 < deviceMenu.getItemCount()) { - deviceMenu.removeAll(); - JMenuItem noDevicesItem = new JMenuItem("No connected devices"); - noDevicesItem.setEnabled(false); - deviceMenu.add(noDevicesItem); - } - devices.setSelectedDevice(null); - } else { - deviceMenu.removeAll(); - - if (selectedDevice == null) { - selectedDevice = deviceList.get(0); - devices.setSelectedDevice(selectedDevice); - } else { - // check if selected device is still connected - boolean found = false; - for (Device device : deviceList) { - if (device.equals(selectedDevice)) { - found = true; - break; - } - } - - if (!found) { - selectedDevice = deviceList.get(0); - devices.setSelectedDevice(selectedDevice); - } - } - - for (final Device device : deviceList) { - final JCheckBoxMenuItem deviceItem = new JCheckBoxMenuItem(device.getName()); - deviceItem.setEnabled(true); - - if (device.equals(selectedDevice)) deviceItem.setState(true); - - // prevent checkboxmenuitem automatic state changing onclick - deviceItem.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (device.equals(devices.getSelectedDevice())) deviceItem.setState(true); - else deviceItem.setState(false); - } - }); - - deviceItem.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - devices.setSelectedDevice(device); - - for (int i = 0; i < deviceMenu.getItemCount(); i++) { - ((JCheckBoxMenuItem) deviceMenu.getItem(i)).setState(false); - } - - deviceItem.setState(true); - } - }); - - deviceMenu.add(deviceItem); - } - } - } - } - - protected AndroidEditor(Base base, String path, EditorState state, Mode mode) throws Exception { - super(base, path, state, mode); - androidMode = (AndroidMode) mode; - androidMode.checkSDK(this); - } - - - public EditorToolbar createToolbar() { - return new AndroidToolbar(this, base); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - public JMenu buildFileMenu() { - String exportPkgTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT, false); - JMenuItem exportPackage = Toolkit.newJMenuItem(exportPkgTitle, 'E'); - exportPackage.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleExportPackage(); - } - }); - - String exportProjectTitle = AndroidToolbar.getTitle(AndroidToolbar.EXPORT, true); - JMenuItem exportProject = Toolkit.newJMenuItemShift(exportProjectTitle, 'E'); - exportProject.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleExportProject(); - } - }); - - return buildFileMenu(new JMenuItem[] { exportPackage, exportProject}); - } - - - public JMenu buildSketchMenu() { - JMenuItem runItem = Toolkit.newJMenuItem(AndroidToolbar.getTitle(AndroidToolbar.RUN, false), 'R'); - runItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleRunDevice(); - } - }); - - JMenuItem presentItem = Toolkit.newJMenuItemShift(AndroidToolbar.getTitle(AndroidToolbar.RUN, true), 'R'); - presentItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleRunEmulator(); - } - }); - - JMenuItem stopItem = new JMenuItem(AndroidToolbar.getTitle(AndroidToolbar.STOP, false)); - stopItem.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - handleStop(); - } - }); - return buildSketchMenu(new JMenuItem[] { runItem, presentItem, stopItem }); - } - - - public JMenu buildModeMenu() { - JMenu menu = new JMenu("Android"); - JMenuItem item; - - item = new JMenuItem("Sketch Permissions"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - new Permissions(sketch); - } - }); - menu.add(item); - - menu.addSeparator(); - - /*item = new JMenuItem("Signing Key Setup"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - new Keys(AndroidEditor.this); - } - }); - item.setEnabled(false); - menu.add(item); */ - - final JMenu deviceMenu = new JMenu("Select device"); - - JMenuItem noDevicesItem = new JMenuItem("No connected devices"); - noDevicesItem.setEnabled(false); - deviceMenu.add(noDevicesItem); - menu.add(deviceMenu); - - // start updating device menus - UpdateDeviceListTask task = new UpdateDeviceListTask(deviceMenu); - java.util.Timer timer = new java.util.Timer(); - timer.schedule(task, 5000, 5000); - - menu.addSeparator(); - - final JMenu sdkMenu = new JMenu("Select target SDK"); - JMenuItem defaultItem = new JCheckBoxMenuItem("No available targets"); - defaultItem.setEnabled(false); - sdkMenu.add(defaultItem); - - new Thread() { - @Override - public void run() { - while(androidMode == null || androidMode.getSDK() == null) { - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - updateSdkMenu(sdkMenu); - } - }.start(); - - menu.add(sdkMenu); - - menu.addSeparator(); - - item = new JMenuItem("Android SDK Manager"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - File file = androidMode.getSDK().getAndroidTool(); - try { - Runtime.getRuntime().exec(new String[] { file.getAbsolutePath(), "sdk" }); - } catch (IOException e1) { - e1.printStackTrace(); - } - } - }); - menu.add(item); - - item = new JMenuItem("Android AVD Manager"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - File file = androidMode.getSDK().getAndroidTool(); - PApplet.exec(new String[] { file.getAbsolutePath(), "avd" }); - } - }); - menu.add(item); - - item = new JMenuItem("Reset Connections"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { -// editor.statusNotice("Resetting the Android Debug Bridge server."); - Devices.killAdbServer(); - } - }); - menu.add(item); - - return menu; - } - - private void updateSdkMenu(final JMenu sdkMenu) { - try { - ArrayList targets = androidMode.getSDK().getAvailableSdkTargets(); - - if (targets.size() != 0) sdkMenu.removeAll(); - - AndroidSDK.SDKTarget lowestTargetAvailable = null; - JCheckBoxMenuItem lowestTargetMenuItem = null; - - String savedTargetVersion = Preferences.get("android.sdk.version"); - boolean savedTargetSet = false; - - for(final AndroidSDK.SDKTarget target : targets) { - final JCheckBoxMenuItem item = new JCheckBoxMenuItem("API " + target.name + " (" + target.version + ")"); - - if (savedTargetSet == false && (lowestTargetAvailable == null || lowestTargetAvailable.version > target.version)) { - lowestTargetAvailable = target; - lowestTargetMenuItem = item; - } - - if (Integer.toString(target.version).equals(savedTargetVersion)) { - AndroidBuild.setSdkTarget(target, sketch); - item.setState(true); - savedTargetSet = true; - } - - item.addChangeListener(new ChangeListener() { - @Override - public void stateChanged(ChangeEvent e) { - if (target.name.equals(AndroidBuild.sdkName)) item.setState(true); - else item.setState(false); - } - }); - - item.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - AndroidBuild.setSdkTarget(target, sketch); - - for (int i = 0; i < sdkMenu.getItemCount(); i++) { - ((JCheckBoxMenuItem) sdkMenu.getItem(i)).setState(false); - } - - item.setState(true); - } - }); - - sdkMenu.add(item); - } - - if (!savedTargetSet) { - AndroidBuild.setSdkTarget(lowestTargetAvailable, sketch); - lowestTargetMenuItem.setState(true); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - - /** - * Uses the main help menu, and adds a few extra options. If/when there's - * Android-specific documentation, we'll switch to that. - */ - public JMenu buildHelpMenu() { - JMenu menu = super.buildHelpMenu(); - JMenuItem item; - - menu.addSeparator(); - - item = new JMenuItem("Processing for Android Wiki"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Base.openURL("http://wiki.processing.org/w/Android"); - } - }); - menu.add(item); - - - item = new JMenuItem("Android Developers Site"); - item.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - Base.openURL("http://developer.android.com/index.html"); - } - }); - menu.add(item); - - return menu; - } - - - /** override the standard grab reference to just show the java reference */ - public void showReference(String filename) { - File javaReferenceFolder = Base.getContentFile("modes/java/reference"); - File file = new File(javaReferenceFolder, filename); - Base.openURL(file.toURI().toString()); - } - - - -// protected void updateMode() { -// // When the selection is made, the menu will update itself -// boolean active = toggleItem.isSelected(); -// if (active) { -// boolean rolling = true; -// if (sdk == null) { -// rolling = loadAndroid(); -// } -// if (rolling) { -// editor.setHandlers(new RunHandler(), new PresentHandler(), -// new StopHandler(), -// new ExportHandler(), new ExportAppHandler()); -// build = new AndroidBuild(editor, sdk); -// editor.statusNotice("Android mode enabled for this editor window."); -// } -// } else { -// editor.resetHandlers(); -// editor.statusNotice("Android mode disabled."); -// } -// } - - -// protected boolean loadAndroid() { -// statusNotice("Loading Android tools."); -// -// try { -// sdk = AndroidSDK.find(this); -// } catch (final Exception e) { -// Base.showWarning("Android Tools Error", e.getMessage(), null); -// statusNotice("Android mode canceled."); -// return false; -// } -// -// // Make sure that the processing.android.core.* classes are available -// if (!checkCore()) { -// statusNotice("Android mode canceled."); -// return false; -// } -// -// statusNotice("Done loading Android tools."); -// return true; -// } - - -// static protected File getCoreZipLocation() { -// if (coreZipLocation == null) { -// coreZipLocation = checkCoreZipLocation(); -// } -// return coreZipLocation; -// } - - -// private boolean checkCore() { -// final File target = getCoreZipLocation(); -// if (!target.exists()) { -// try { -// final URL url = new URL(ANDROID_CORE_URL); -// PApplet.saveStream(target, url.openStream()); -// } catch (final Exception e) { -// Base.showWarning("Download Error", -// "Could not download Android core.zip", e); -// return false; -// } -// } -// return true; -// } - - - public void statusError(String what) { - super.statusError(what); -// new Exception("deactivating RUN").printStackTrace(); - toolbar.deactivate(AndroidToolbar.RUN); - } - - - public void sketchStopped() { - deactivateRun(); - statusEmpty(); - } - - - /** - * Build the sketch and run it inside an emulator with the debugger. - */ - public void handleRunEmulator() { - new Thread() { - public void run() { - toolbar.activate(AndroidToolbar.RUN); - startIndeterminate(); - prepareRun(); - try { - androidMode.handleRunEmulator(sketch, AndroidEditor.this); - } catch (SketchException e) { - statusError(e); - } catch (IOException e) { - statusError(e); - } - stopIndeterminate(); - } - }.start(); - } - - - /** - * Build the sketch and run it on a device with the debugger connected. - */ - public void handleRunDevice() { - if(Base.isWindows() && !Preferences.getBoolean("usbDriverWarningShown")) { - Preferences.setBoolean("usbDriverWarningShown", true); - - String message = ""; - File usbDriverFile = new File(((AndroidMode) sketch.getMode()).getSDK().getSdkFolder(), "extras/google/usb_driver"); - if (usbDriverFile.exists()) { - message = "" + - "You might need to install Google USB Driver to run the sketch on your device.
    " + - "Please follow the guide at http://developer.android.com/tools/extras/oem-usb.html#InstallingDriver to install the driver.
    " + - "For your reference, the driver is located in: " + usbDriverFile.getAbsolutePath(); - } else { - message = "" + - "You might need to install Google USB Driver to run the sketch on your device.
    " + - "Please follow the guide at http://developer.android.com/tools/extras/oem-usb.html#InstallingDriver to install the driver.
    " + - "You will also need to download the driver from http://developer.android.com/sdk/win-usb.html"; - } - - Base.showWarning( - "USB Driver warning", - message - ); - } else { - new Thread() { - public void run() { - toolbar.activate(AndroidToolbar.RUN); - startIndeterminate(); - prepareRun(); - try { - androidMode.handleRunDevice(sketch, AndroidEditor.this); - } catch (SketchException e) { - statusError(e); - } catch (IOException e) { - statusError(e); - } - stopIndeterminate(); - } - }.start(); - } - } - - - public void handleStop() { - toolbar.deactivate(AndroidToolbar.RUN); - stopIndeterminate(); - androidMode.handleStop(this); - } - - - /** - * Create a release build of the sketch and have its apk files ready. - * If users want a debug build, they can do that from the command line. - */ - public void handleExportProject() { - if (handleExportCheckModified()) { - new Thread() { - public void run() { - toolbar.activate(AndroidToolbar.EXPORT); - startIndeterminate(); - statusNotice("Exporting a debug version of the sketch..."); - AndroidBuild build = new AndroidBuild(sketch, androidMode); - try { - File exportFolder = build.exportProject(); - if (exportFolder != null) { - Base.openFolder(exportFolder); - statusNotice("Done with export."); - } - } catch (IOException e) { - statusError(e); - } catch (SketchException e) { - statusError(e); - } - stopIndeterminate(); - toolbar.deactivate(AndroidToolbar.EXPORT); - } - }.start(); - } - -// try { -// buildReleaseForExport("debug"); -// } catch (final MonitorCanceled ok) { -// statusNotice("Canceled."); -// } finally { -// deactivateExport(); -// } - } - - - /** - * Create a release build of the sketch and install its apk files on the - * attached device. - */ - public void handleExportPackage() { - // Need to implement an entire signing setup first - // http://dev.processing.org/bugs/show_bug.cgi?id=1430 - if (handleExportCheckModified()) { -// deactivateExport(); - new KeyStoreManager(this); - } - } - - public void startExportPackage(final String keyStorePassword) { - new Thread() { - public void run() { - startIndeterminate(); - statusNotice("Exporting signed package..."); - AndroidBuild build = new AndroidBuild(sketch, androidMode); - try { - File projectFolder = build.exportPackage(keyStorePassword); - if (projectFolder != null) { - statusNotice("Done with export."); - Base.openFolder(projectFolder); - } else { - statusError("Error with export"); - } - } catch (IOException e) { - statusError(e); - } catch (SketchException e) { - statusError(e); - } catch (InterruptedException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - stopIndeterminate(); - } - }.start(); - } -} diff --git a/src/processing/mode/android/AndroidKeyStore.java b/src/processing/mode/android/AndroidKeyStore.java deleted file mode 100644 index 8a6983bf9..000000000 --- a/src/processing/mode/android/AndroidKeyStore.java +++ /dev/null @@ -1,84 +0,0 @@ -package processing.mode.android; - -import processing.app.Base; - -import java.io.File; - -/** - * Created with IntelliJ IDEA. - * User: imilka - * Date: 27.05.14 - * Time: 14:38 - */ -public class AndroidKeyStore { - - public static final String ALIAS_STRING = "processing-keystore"; - public static final String KEYSTORE_FILE_NAME = "android-release-key.keystore"; - - public static File getKeyStore() { - File keyStore = getKeyStoreLocation(); - if (!keyStore.exists()) return null; - return keyStore; - } - - public static File getKeyStoreLocation() { - File sketchbookFolder = processing.app.Base.getSketchbookFolder(); - File keyStoreFolder = new File(sketchbookFolder, "keystore"); - if (!keyStoreFolder.exists()) { - boolean result = keyStoreFolder.mkdirs(); - - if (!result) { - Base.showWarning("Folders, folders, folders", - "Could not create the necessary folders to build.\n" + - "Perhaps you have some file permissions to sort out?", null); - return null; - } - } - - File keyStore = new File(keyStoreFolder, KEYSTORE_FILE_NAME); - return keyStore; - } - - public static void generateKeyStore(String password, - String commonName, String organizationalUnit, - String organizationName, String locality, - String state, String country) throws Exception { - String dnamePlaceholder = "CN=%s, OU=%s, O=%s, L=%s, S=%s, C=%s"; - String dname = String.format(dnamePlaceholder, - parseDnameField(commonName), parseDnameField(organizationalUnit), parseDnameField(organizationName), - parseDnameField(locality), parseDnameField(state), parseDnameField(country)); - - String[] args = { - System.getProperty("java.home") - + System.getProperty("file.separator") + "bin" - + System.getProperty("file.separator") + "keytool", "-genkey", - "-keystore", getKeyStoreLocation().getAbsolutePath(), - "-alias", ALIAS_STRING, - "-keyalg", "RSA", - "-keysize", "2048", - "-validity", "10000", - "-keypass", password, - "-storepass", password, - "-dname", dname - }; - - Process generation = Runtime.getRuntime().exec(args); - generation.waitFor(); - - if (getKeyStore() == null) throw new Exception(); - } - - public static boolean resetKeyStore() { - File keyStore = getKeyStore(); - if (keyStore == null) return true; - - File keyStoreBackup = new File(processing.app.Base.getSketchbookFolder(), "keystore/" + KEYSTORE_FILE_NAME + "-" + AndroidMode.getDateStamp()); - if (!keyStore.renameTo(keyStoreBackup)) return false; - return true; - } - - private static String parseDnameField(String content) { - if (content == null || content.length() == 0) return "Unknown"; - else return content; - } -} diff --git a/src/processing/mode/android/AndroidMode.java b/src/processing/mode/android/AndroidMode.java deleted file mode 100644 index 80dd16587..000000000 --- a/src/processing/mode/android/AndroidMode.java +++ /dev/null @@ -1,310 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2011-12 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.mode.android; - -import processing.app.*; -import processing.mode.java.JavaMode; - -import java.io.File; -import java.io.IOException; -import java.text.SimpleDateFormat; -import java.util.Date; - - -public class AndroidMode extends JavaMode { - private AndroidSDK sdk; - private File coreZipLocation; - private AndroidRunner runner; - - public static boolean sdkDownloadInProgress = false; - - public AndroidMode(Base base, File folder) { - super(base, folder); - } - - - @Override - public Editor createEditor(Base base, String path, EditorState state) { - try { - return new AndroidEditor(base, path, state, this); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - - @Override - public String getTitle() { - return "Android"; - } - - - public File[] getKeywordFiles() { - return new File[] { - Base.getContentFile("modes/java/keywords.txt") - }; - } - - - public File[] getExampleCategoryFolders() { - return new File[] { - new File(examplesFolder, "Basics"), - new File(examplesFolder, "Topics"), - new File(examplesFolder, "Demos"), - new File(examplesFolder, "Sensors") - }; - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - /** @return null so that it doesn't try to pass along the desktop version of core.jar */ - public Library getCoreLibrary() { - return null; - } - - - protected File getCoreZipLocation() { - if (coreZipLocation == null) { - /* - // for debugging only, check to see if this is an svn checkout - File debugFile = new File("../../../android/core.zip"); - if (!debugFile.exists() && Base.isMacOS()) { - // current path might be inside Processing.app, so need to go much higher - debugFile = new File("../../../../../../../android/core.zip"); - } - if (debugFile.exists()) { - System.out.println("Using version of core.zip from local SVN checkout."); -// return debugFile; - coreZipLocation = debugFile; - } - */ - - // otherwise do the usual - // return new File(base.getSketchbookFolder(), ANDROID_CORE_FILENAME); - coreZipLocation = getContentFile("android-core.zip"); - } - return coreZipLocation; - } - - -// public AndroidSDK loadSDK() throws BadSDKException, IOException { -// if (sdk == null) { -// sdk = AndroidSDK.load(); -// } -// return sdk; -// } - - public void loadSDK() { - try { - sdk = AndroidSDK.load(); - } catch (BadSDKException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void checkSDK(Editor parent) { - if (sdk == null) { - try { - sdk = AndroidSDK.load(); - // FIXME REVERT THIS STATEMENT AFTER TESTING (should be ==) - if (sdk == null) { - sdk = AndroidSDK.locate(parent, this); - } - } catch (BadSDKException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - if (sdk == null) { - if (!sdkDownloadInProgress) { - Base.showWarning("It's gonna be a bad day", - "The Android SDK could not be loaded.\n" + - "Use of Android mode will be all but disabled.", - null); - } - } - } - - - public AndroidSDK getSDK() { - return sdk; - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - - static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMdd.HHmm"); - - - static public String getDateStamp() { - return dateFormat.format(new Date()); - } - - - static public String getDateStamp(long stamp) { - return dateFormat.format(new Date(stamp)); - } - - - // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - -// public void handleRun(Sketch sketch, RunnerListener listener) throws SketchException { -// JavaBuild build = new JavaBuild(sketch); -// String appletClassName = build.build(); -// if (appletClassName != null) { -// runtime = new Runner(build, listener); -// runtime.launch(false); -// } -// } - public void handleRunEmulator(Sketch sketch, RunnerListener listener) throws SketchException, IOException { - listener.startIndeterminate(); - listener.statusNotice("Starting build..."); - AndroidBuild build = new AndroidBuild(sketch, this); - - listener.statusNotice("Building Android project..."); - build.build("debug"); - - boolean avd = AVD.ensureProperAVD(sdk); - if (!avd) { - SketchException se = - new SketchException("Could not create a virtual device for the emulator."); - se.hideStackTrace(); - throw se; - } - - listener.statusNotice("Running sketch on emulator..."); - runner = new AndroidRunner(build, listener); - runner.launch(Devices.getInstance().getEmulator()); - } - - - public void handleRunDevice(Sketch sketch, RunnerListener listener) throws SketchException, IOException { -// JavaBuild build = new JavaBuild(sketch); -// String appletClassName = build.build(); -// if (appletClassName != null) { -// runtime = new Runner(build, listener); -// runtime.launch(true); -// } - -// try { -// runSketchOnDevice(Environment.getInstance().getHardware(), "debug", this); -// } catch (final MonitorCanceled ok) { -// sketchStopped(); -// statusNotice("Canceled."); -// } - listener.startIndeterminate(); - listener.statusNotice("Starting build..."); - AndroidBuild build = new AndroidBuild(sketch, this); - - listener.statusNotice("Building Android project..."); - build.build("debug"); - - listener.statusNotice("Running sketch on device..."); - runner = new AndroidRunner(build, listener); - runner.launch(Devices.getInstance().getHardware()); - } - - - public void handleStop(RunnerListener listener) { - listener.statusNotice(""); - listener.stopIndeterminate(); - -// if (runtime != null) { -// runtime.close(); // kills the window -// runtime = null; // will this help? -// } - if (runner != null) { - runner.close(); - runner = null; - } - } - - -// public void handleExport(Sketch sketch, ) - - - /* - protected void buildReleaseForExport(Sketch sketch, String target) throws MonitorCanceled { -// final IndeterminateProgressMonitor monitor = -// new IndeterminateProgressMonitor(this, -// "Building and exporting...", -// "Creating project..."); - try { - AndroidBuild build = new AndroidBuild(sketch, sdk); - File tempFolder = null; - try { - tempFolder = build.createProject(target, getCoreZipLocation()); - if (tempFolder == null) { - return; - } - } catch (IOException e) { - e.printStackTrace(); - } catch (SketchException se) { - se.printStackTrace(); - } - try { - if (monitor.isCanceled()) { - throw new MonitorCanceled(); - } - monitor.setNote("Building release version..."); -// if (!build.antBuild("release")) { -// return; -// } - - if (monitor.isCanceled()) { - throw new MonitorCanceled(); - } - - // If things built successfully, copy the contents to the export folder - File exportFolder = build.createExportFolder(); - if (exportFolder != null) { - Base.copyDir(tempFolder, exportFolder); - listener.statusNotice("Done with export."); - Base.openFolder(exportFolder); - } else { - listener.statusError("Could not copy files to export folder."); - } - } catch (IOException e) { - listener.statusError(e); - - } finally { - build.cleanup(); - } - } finally { - monitor.close(); - } - } - - - @SuppressWarnings("serial") - private static class MonitorCanceled extends Exception { - } - */ -} \ No newline at end of file diff --git a/src/processing/mode/android/AndroidPreprocessor.java b/src/processing/mode/android/AndroidPreprocessor.java deleted file mode 100644 index 13e6058f0..000000000 --- a/src/processing/mode/android/AndroidPreprocessor.java +++ /dev/null @@ -1,277 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2009-10 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.mode.android; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.Writer; -import java.util.List; - -import processing.app.*; -import processing.core.PApplet; -import processing.mode.java.preproc.PdePreprocessor; -import processing.mode.java.preproc.PreprocessorResult; -import antlr.RecognitionException; -import antlr.TokenStreamException; - - -public class AndroidPreprocessor extends PdePreprocessor { - Sketch sketch; - String packageName; - - protected String smoothStatement; - protected String sketchQuality; - - - public static final String SMOOTH_REGEX = - "(?:^|\\s|;)smooth\\s*\\(\\s*([^\\s,]+)\\s*\\)\\s*\\;"; - - public AndroidPreprocessor(final Sketch sketch, - final String packageName) throws IOException { - super(sketch.getName()); - this.sketch = sketch; - this.packageName = packageName; - } - - - public String[] initSketchSize(String code) throws SketchException { - String[] info = parseSketchSize(code, true); - if (info == null) { - System.err.println("More about the size() command on Android can be"); - System.err.println("found here: http://wiki.processing.org/w/Android"); - throw new SketchException("Could not parse the size() command."); - } - sizeStatement = info[0]; - sketchWidth = info[1]; - sketchHeight = info[2]; - sketchRenderer = info[3]; - return info; - } - - - public String[] initSketchSmooth(String code) throws SketchException { - String[] info = parseSketchSmooth(code, true); - if (info == null) { - System.err.println("More about the size() command on Android can be"); - System.err.println("found here: http://wiki.processing.org/w/Android"); - throw new SketchException("Could not parse the size() command."); - } - smoothStatement = info[0]; - sketchQuality = info[1]; - return info; - } - - - static public String[] parseSketchSmooth(String code, boolean fussy) { - String[] matches = PApplet.match(scrubComments(code), SMOOTH_REGEX); - - if (matches != null) { - boolean badSmooth = false; - - if (PApplet.parseInt(matches[1], -1) == -1) { - badSmooth = true; - } - - if (badSmooth && fussy) { - // found a reference to smooth, but it didn't seem to contain numbers - final String message = - "The smooth level of this applet could not automatically\n" + - "be determined from your code. Use only a numeric\n" + - "value (not variables) for the smooth() command.\n" + - "See the smooth() reference for an explanation."; - Base.showWarning("Could not find smooth level", message, null); -// new Exception().printStackTrace(System.out); - return null; - } - - return matches; - } - return new String[] { null, null }; // not an error, just empty - } - - - /* - protected boolean parseSketchSize() { - // This matches against any uses of the size() function, whether numbers - // or variables or whatever. This way, no warning is shown if size() isn't - // actually used in the applet, which is the case especially for anyone - // who is cutting/pasting from the reference. - - String scrubbed = processing.mode.java.JavaBuild.scrubComments(sketch.getCode(0).getProgram()); - String[] matches = PApplet.match(scrubbed, processing.mode.java.JavaBuild.SIZE_REGEX); -// PApplet.println("matches: " + Sketch.SIZE_REGEX); -// PApplet.println(matches); - - if (matches != null) { - boolean badSize = false; - - if (matches[1].equals("screenWidth") || - matches[1].equals("screenHeight") || - matches[2].equals("screenWidth") || - matches[2].equals("screenHeight")) { - final String message = - "The screenWidth and screenHeight variables are named\n" + - "displayWidth and displayHeight in this release of Processing."; - Base.showWarning("Time for a quick update", message, null); - return false; - } - - if (!matches[1].equals("displayWidth") && - !matches[1].equals("displayHeight") && - PApplet.parseInt(matches[1], -1) == -1) { - badSize = true; - } - if (!matches[2].equals("displayWidth") && - !matches[2].equals("displayHeight") && - PApplet.parseInt(matches[2], -1) == -1) { - badSize = true; - } - - if (badSize) { - // found a reference to size, but it didn't seem to contain numbers - final String message = - "The size of this applet could not automatically be determined\n" + - "from your code. Use only numeric values (not variables) for the\n" + - "size() command. See the size() reference for more information."; - Base.showWarning("Could not find sketch size", message, null); - System.out.println("More about the size() command on Android can be"); - System.out.println("found here: http://wiki.processing.org/w/Android"); - return false; - } - -// PApplet.println(matches); - sizeStatement = matches[0]; // the full method to be removed from the source - sketchWidth = matches[1]; - sketchHeight = matches[2]; - sketchRenderer = matches[3].trim(); - if (sketchRenderer.length() == 0) { - sketchRenderer = null; - } - } else { - sizeStatement = null; - sketchWidth = null; - sketchHeight = null; - sketchRenderer = null; - } - return true; - } - */ - - - public PreprocessorResult write(Writer out, String program, String[] codeFolderPackages) - throws SketchException, RecognitionException, TokenStreamException { - if (sizeStatement != null) { - int start = program.indexOf(sizeStatement); - program = program.substring(0, start) + - program.substring(start + sizeStatement.length()); - } - // the OpenGL package is back in 2.0a5 - //program = program.replaceAll("import\\s+processing\\.opengl\\.\\S+;", ""); - return super.write(out, program, codeFolderPackages); - } - - - @Override - protected int writeImports(final PrintWriter out, - final List programImports, - final List codeFolderImports) { - out.println("package " + packageName + ";"); - out.println(); - // add two lines for the package above - return 2 + super.writeImports(out, programImports, codeFolderImports); - } - - - protected void writeFooter(PrintWriter out, String className) { - if (mode == Mode.STATIC) { - // close off draw() definition - out.println("noLoop();"); - out.println(indent + "}"); - } - - if ((mode == Mode.STATIC) || (mode == Mode.ACTIVE)) { - out.println(); - - if (sketchWidth != null) { - out.println(indent + "public int sketchWidth() { return " + sketchWidth + "; }"); - } - if (sketchHeight != null) { - out.println(indent + "public int sketchHeight() { return " + sketchHeight + "; }"); - } - if (sketchRenderer != null) { - out.println(indent + "public String sketchRenderer() { return " + sketchRenderer + "; }"); - } - - if (sketchQuality != null) { - out.println(indent + "public int sketchQuality() { return " + sketchQuality + "; }"); - } - - // close off the class definition - out.println("}"); - } - } - - - // As of revision 0215 (2.0b7-ish), the default imports are now identical - // between desktop and Android (to avoid unintended incompatibilities). - /* - @Override - public String[] getCoreImports() { - return new String[] { - "processing.core.*", - "processing.data.*", - "processing.event.*", - "processing.opengl.*" - }; - } - - - @Override - public String[] getDefaultImports() { - final String prefsLine = Preferences.get("android.preproc.imports"); - if (prefsLine != null) { - return PApplet.splitTokens(prefsLine, ", "); - } - - // The initial values are stored in here for the day when Android - // is broken out as a separate mode. - - // In the future, this may include standard classes for phone or - // accelerometer access within the Android APIs. This is currently living - // in code rather than preferences.txt because Android mode needs to - // maintain its independence from the rest of processing.app. - final String[] androidImports = new String[] { -// "android.view.MotionEvent", "android.view.KeyEvent", -// "android.graphics.Bitmap", //"java.awt.Image", - "java.io.*", // for BufferedReader, InputStream, etc - //"java.net.*", "java.text.*", // leaving otu for now - "java.util.*" // for ArrayList and friends - //"java.util.zip.*", "java.util.regex.*" // not necessary w/ newer i/o - }; - - Preferences.set("android.preproc.imports", - PApplet.join(androidImports, ",")); - - return androidImports; - } - */ -} \ No newline at end of file diff --git a/src/processing/mode/android/AndroidRunner.java b/src/processing/mode/android/AndroidRunner.java deleted file mode 100644 index 951026a20..000000000 --- a/src/processing/mode/android/AndroidRunner.java +++ /dev/null @@ -1,312 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2011 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.mode.android; - -import java.io.PrintStream; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import processing.app.Editor; -import processing.app.RunnerListener; -import processing.app.SketchException; -import processing.mode.java.runner.Runner; - - -public class AndroidRunner implements DeviceListener { - AndroidBuild build; - RunnerListener listener; - - protected PrintStream sketchErr; - protected PrintStream sketchOut; - - - public AndroidRunner(AndroidBuild build, RunnerListener listener) { - this.build = build; - this.listener = listener; - - if (listener instanceof Editor) { - Editor editor = (Editor) listener; - sketchErr = editor.getConsole().getErr(); - sketchOut = editor.getConsole().getOut(); - } else { - sketchErr = System.err; - sketchOut = System.out; - } - } - - - public void launch(Future deviceFuture) { -// try { -// runSketchOnDevice(Devices.getInstance().getEmulator(), "debug", AndroidEditor.this); -// } catch (final MonitorCanceled ok) { -// sketchStopped(); -// statusNotice("Canceled."); -// } - - listener.statusNotice("Waiting for device to become available..."); -// final Device device = waitForDevice(deviceFuture, monitor); - final Device device = waitForDevice(deviceFuture, listener); - if (device == null || !device.isAlive()) { - listener.statusError("Lost connection with device while launching. Try again."); - // Reset the server, in case that's the problem. Sometimes when - // launching the emulator times out, the device list refuses to update. - Devices.killAdbServer(); - return; - } - - device.addListener(this); - -// if (listener.isHalted()) { -//// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } - -// monitor.setNote("Installing sketch on " + device.getId()); - listener.statusNotice("Installing sketch on " + device.getId()); - // this stopped working with Android SDK tools revision 17 - if (!device.installApp(build, listener)) { - listener.statusError("Lost connection with device while installing. Try again."); - Devices.killAdbServer(); // see above - return; - } -// if (!build.antInstall()) { -// } - -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Starting sketch on " + device.getId()); - listener.statusNotice("Starting sketch on " + device.getId()); - if (startSketch(build, device)) { - listener.statusNotice("Sketch launched on the " - + (device.isEmulator() ? "emulator" : "device") + "."); - } else { - listener.statusError("Could not start the sketch."); - } - listener.stopIndeterminate(); - lastRunDevice = device; -//} finally { -// build.cleanup(); -//} -//} finally { -////monitor.close(); -//listener.stopIndeterminate(); -//} - } - - - private volatile Device lastRunDevice = null; - - /** - * @param target "debug" or "release" - */ - /* - private void runSketchOnDevice(Sketch sketch, - Future deviceFuture, - String target, - RunnerListener listener) { -// final IndeterminateProgressMonitor monitor = -// new IndeterminateProgressMonitor(this, -// "Building and launching...", -// "Creating project..."); - - - AndroidBuild build = new AndroidBuild(sketch, listener); - try { - try { - if (build.createProject(target) == null) { - return; - } - } catch (SketchException se) { - listener.statusError(se); - } catch (IOException e) { - listener.statusError(e); - } - try { -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Building..."); - listener.statusNotice("Building..."); - try { - if (!build.antBuild(target)) { - return; - } - } catch (SketchException se) { - listener.statusError(se); - } - -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Waiting for device to become available..."); - listener.statusNotice("Waiting for device to become available..."); -// final Device device = waitForDevice(deviceFuture, monitor); - final Device device = waitForDevice(deviceFuture, listener); - if (device == null || !device.isAlive()) { - listener.statusError("Device killed or disconnected."); - return; - } - - device.addListener(this); - -// if (listener.isHalted()) { -//// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } - -// monitor.setNote("Installing sketch on " + device.getId()); - listener.statusNotice("Installing sketch on " + device.getId()); - if (!device.installApp(build.getPathForAPK(target), listener)) { - listener.statusError("Device killed or disconnected."); - return; - } - -// if (monitor.isCanceled()) { -// throw new MonitorCanceled(); -// } -// monitor.setNote("Starting sketch on " + device.getId()); - listener.statusNotice("Starting sketch on " + device.getId()); - if (startSketch(build, device)) { - listener.statusNotice("Sketch launched on the " - + (device.isEmulator() ? "emulator" : "device") + "."); - } else { - listener.statusError("Could not start the sketch."); - } - - lastRunDevice = device; - } finally { - build.cleanup(); - } - } finally { -// monitor.close(); - listener.stopIndeterminate(); - } - } - */ - - - // if user asks for 480x320, 320x480, 854x480 etc, then launch like that - // though would need to query the emulator to see if it can do that - - private boolean startSketch(AndroidBuild build, final Device device) { - final String packageName = build.getPackageName(); - final String className = build.getSketchClassName(); - try { - if (device.launchApp(packageName, className)) { - return true; - } - } catch (final Exception e) { - e.printStackTrace(System.err); - } - return false; - } - - - private Device waitForDevice(Future deviceFuture, RunnerListener listener) { - for (int i = 0; i < 120; i++) { -// if (monitor.isCanceled()) { - if (listener.isHalted()) { - deviceFuture.cancel(true); -// throw new MonitorCanceled(); - return null; - } - try { - return deviceFuture.get(1, TimeUnit.SECONDS); - } catch (final InterruptedException e) { - listener.statusError("Interrupted."); - return null; - } catch (final ExecutionException e) { - listener.statusError(e); - return null; - } catch (final TimeoutException expected) { - } - } - listener.statusError("No, on second thought, I'm giving up " + - "on waiting for that device to show up."); - return null; - } - - - private static final Pattern LOCATION = - Pattern.compile("\\(([^:]+):(\\d+)\\)"); - private static final Pattern EXCEPTION_PARSER = - Pattern.compile("^\\s*([a-z]+(?:\\.[a-z]+)+)(?:: .+)?$", - Pattern.CASE_INSENSITIVE); - - /** - * Currently figures out the first relevant stack trace line - * by looking for the telltale presence of "processing.android" - * in the package. If the packaging for droid sketches changes, - * this method will have to change too. - */ - public void stackTrace(final List trace) { - final Iterator frames = trace.iterator(); - final String exceptionLine = frames.next(); - - final Matcher m = EXCEPTION_PARSER.matcher(exceptionLine); - if (!m.matches()) { - System.err.println("Can't parse this exception line:"); - System.err.println(exceptionLine); - listener.statusError("Unknown exception"); - return; - } - final String exceptionClass = m.group(1); -// if (Runner.handleCommonErrors(exceptionClass, exceptionLine, listener)) { -// return; -// } - Runner.handleCommonErrors(exceptionClass, exceptionLine, listener, sketchErr); - - while (frames.hasNext()) { - final String line = frames.next(); - if (line.contains("processing.android")) { - final Matcher lm = LOCATION.matcher(line); - if (lm.find()) { - final String filename = lm.group(1); - final int lineNumber = Integer.parseInt(lm.group(2)) - 1; - final SketchException rex = - build.placeException(exceptionLine, filename, lineNumber); - listener.statusError(rex == null ? new SketchException(exceptionLine, false) : rex); - return; - } - } - } - } - - - // called by AndroidMode.handleStop()... - public void close() { - if (lastRunDevice != null) { - lastRunDevice.bringLauncherToFront(); - } - } - - - // sketch stopped on the device - public void sketchStopped() { - listener.stopIndeterminate(); - listener.statusHalt(); - } -} diff --git a/src/processing/mode/android/AndroidSDK.java b/src/processing/mode/android/AndroidSDK.java deleted file mode 100644 index f68fabd63..000000000 --- a/src/processing/mode/android/AndroidSDK.java +++ /dev/null @@ -1,431 +0,0 @@ -package processing.mode.android; - -import processing.app.Base; -import processing.app.Platform; -import processing.app.Preferences; -import processing.app.exec.ProcessHelper; -import processing.app.exec.ProcessResult; -import processing.core.PApplet; - -import javax.swing.*; -import java.awt.*; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; - -class AndroidSDK { - private final File folder; - private final File tools; - private final File platforms; - private final File platformTools; - private final File androidTool; - - private static final String ANDROID_SDK_PRIMARY = - "Is the Android SDK installed?"; - - private static final String ANDROID_SDK_SECONDARY = - "The Android SDK does not appear to be installed,
    " + - "because the ANDROID_SDK variable is not set.
    " + - "If it is installed, click “Locate SDK path” to select the
    " + - "location of the SDK, or “Download SDK” to let
    " + - "Processing download SDK automatically.

    " + - "If you want to download SDK manually, you can visit
    "+ - "download site at http://developer.android.com/sdk."; - - private static final String SELECT_ANDROID_SDK_FOLDER = - "Choose the location of the Android SDK"; - - private static final String NOT_ANDROID_SDK = - "The selected folder does not appear to contain an Android SDK,\n" + - "or the SDK needs to be updated to the latest version."; - -// private static final String ANDROID_SDK_URL = -// "http://developer.android.com/sdk/"; - - - public AndroidSDK(File folder) throws BadSDKException, IOException { - this.folder = folder; - if (!folder.exists()) { - throw new BadSDKException(folder + " does not exist"); - } - - tools = new File(folder, "tools"); - if (!tools.exists()) { - throw new BadSDKException("There is no tools folder in " + folder); - } - - platformTools = new File(folder, "platform-tools"); - if (!platformTools.exists()) { - throw new BadSDKException("There is no platform-tools folder in " + folder); - } - - platforms = new File(folder, "platforms"); - if (!platforms.exists()) { - throw new BadSDKException("There is no platforms folder in " + folder); - } - - androidTool = findAndroidTool(tools); - - final Platform p = Base.getPlatform(); - - String path = p.getenv("PATH"); - - p.setenv("ANDROID_SDK", folder.getCanonicalPath()); - path = platformTools.getCanonicalPath() + File.pathSeparator + - tools.getCanonicalPath() + File.pathSeparator + path; - - String javaHomeProp = System.getProperty("java.home"); - File javaHome = new File(javaHomeProp).getCanonicalFile(); - p.setenv("JAVA_HOME", javaHome.getCanonicalPath()); - - path = new File(javaHome, "bin").getCanonicalPath() + File.pathSeparator + path; - - p.setenv("PATH", path); - - checkDebugCertificate(); - } - - - /** - * If a debug certificate exists, check its expiration date. If it's expired, - * remove it so that it doesn't cause problems during the build. - */ - protected void checkDebugCertificate() { - File dotAndroidFolder = new File(System.getProperty("user.home"), ".android"); - File keystoreFile = new File(dotAndroidFolder, "debug.keystore"); - if (keystoreFile.exists()) { - // keytool -list -v -storepass android -keystore debug.keystore - ProcessHelper ph = new ProcessHelper(new String[] { - "keytool", "-list", "-v", - "-storepass", "android", - "-keystore", keystoreFile.getAbsolutePath() - }); - try { - ProcessResult result = ph.execute(); - if (result.succeeded()) { - // Valid from: Mon Nov 02 15:38:52 EST 2009 until: Tue Nov 02 16:38:52 EDT 2010 - String[] lines = PApplet.split(result.getStdout(), '\n'); - for (String line : lines) { - String[] m = PApplet.match(line, "Valid from: .* until: (.*)"); - if (m != null) { - String timestamp = m[1].trim(); - // "Sun Jan 22 11:09:08 EST 2012" - // Hilariously, this is the format of Date.toString(), however - // it isn't the default for SimpleDateFormat or others. Yay! - DateFormat df = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy"); - try { - Date date = df.parse(timestamp); - long expireMillis = date.getTime(); - if (expireMillis < System.currentTimeMillis()) { - System.out.println("Removing expired debug.keystore file."); - String hidingName = "debug.keystore." + AndroidMode.getDateStamp(expireMillis); - File hidingFile = new File(keystoreFile.getParent(), hidingName); - if (!keystoreFile.renameTo(hidingFile)) { - System.err.println("Could not remove the expired debug.keystore file."); - System.err.println("Please remove the file " + keystoreFile.getAbsolutePath()); - } -// } else { -// System.out.println("Nah, that won't expire until " + date); //timestamp); - } - } catch (ParseException pe) { - System.err.println("The date “" + timestamp + "” could not be parsed."); - System.err.println("Please report this as a bug so we can fix it."); - } - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - - public File getAndroidTool() { - return androidTool; - } - - - public String getAndroidToolPath() { - return androidTool.getAbsolutePath(); - } - - - public File getSdkFolder() { - return folder; - } - - - /* - public File getToolsFolder() { - return tools; - } - */ - - - public File getPlatformToolsFolder() { - return platformTools; - } - - - /** - * Checks a path to see if there's a tools/android file inside, a rough check - * for the SDK installation. Also figures out the name of android/android.bat - * so that it can be called explicitly. - */ - private static File findAndroidTool(final File tools) throws BadSDKException { - if (new File(tools, "android.exe").exists()) { - return new File(tools, "android.exe"); - } - if (new File(tools, "android.bat").exists()) { - return new File(tools, "android.bat"); - } - if (new File(tools, "android").exists()) { - return new File(tools, "android"); - } - throw new BadSDKException("Cannot find the android tool in " + tools); - } - - - /** - * Check for the ANDROID_SDK environment variable. If the variable is set, - * and refers to a legitimate Android SDK, then use that and save the pref. - * - * Check for a previously set android.sdk.path preference. If the pref - * is set, and refers to a legitimate Android SDK, then use that. - * - * Prompt the user to select an Android SDK. If the user selects a - * legitimate Android SDK, then use that, and save the preference. - * - * @return an AndroidSDK - * @throws BadSDKException - * @throws IOException - */ - public static AndroidSDK load() throws BadSDKException, IOException { - final Platform platform = Base.getPlatform(); - - // The environment variable is king. The preferences.txt entry is a page. - final String sdkEnvPath = platform.getenv("ANDROID_SDK"); - if (sdkEnvPath != null) { - try { - final AndroidSDK androidSDK = new AndroidSDK(new File(sdkEnvPath)); - // Set this value in preferences.txt, in case ANDROID_SDK - // gets knocked out later. For instance, by that pesky Eclipse, - // which nukes all env variables when launching from the IDE. - Preferences.set("android.sdk.path", sdkEnvPath); - return androidSDK; - } catch (final BadSDKException drop) { } - } - - // If android.sdk.path exists as a preference, make sure that the folder - // is not bogus, otherwise the SDK may have been removed or deleted. - final String sdkPrefsPath = Preferences.get("android.sdk.path"); - if (sdkPrefsPath != null) { - try { - final AndroidSDK androidSDK = new AndroidSDK(new File(sdkPrefsPath)); - // Set this value in preferences.txt, in case ANDROID_SDK - // gets knocked out later. For instance, by that pesky Eclipse, - // which nukes all env variables when launching from the IDE. - Preferences.set("android.sdk.path", sdkPrefsPath); - return androidSDK; - } catch (final BadSDKException wellThatsThat) { - Preferences.unset("android.sdk.path"); - } - } - return null; - } - - - static public AndroidSDK locate(final Frame window, final AndroidMode androidMode) - throws BadSDKException, IOException { - final int result = showLocateDialog(window); - if (result == JOptionPane.CANCEL_OPTION) { - throw new BadSDKException("User canceled attempt to find SDK."); - } - if (result == JOptionPane.YES_OPTION) { - // here we are going to download sdk automatically - //Base.openURL(ANDROID_SDK_URL); - //throw new BadSDKException("No SDK installed."); - - return download(androidMode); - } - while (true) { - // TODO this is really a yucky way to do this stuff. fix it. - File folder = selectFolder(SELECT_ANDROID_SDK_FOLDER, null, window); - if (folder == null) { - throw new BadSDKException("User canceled attempt to find SDK."); - } - try { - final AndroidSDK androidSDK = new AndroidSDK(folder); - Preferences.set("android.sdk.path", folder.getAbsolutePath()); - return androidSDK; - - } catch (final BadSDKException nope) { - JOptionPane.showMessageDialog(window, NOT_ANDROID_SDK); - } - } - } - - static public AndroidSDK download(final AndroidMode androidMode) throws BadSDKException { - AndroidMode.sdkDownloadInProgress = true; - - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - SDKDownloader downloader = new SDKDownloader(androidMode); - downloader.startDownload(); - } - }); - return null; - } - - static public int showLocateDialog(Frame editor) { - // Pane formatting adapted from the Quaqua guide - // http://www.randelshofer.ch/quaqua/guide/joptionpane.html - JOptionPane pane = - new JOptionPane(" " + - " " + - "" + ANDROID_SDK_PRIMARY + "" + - "

    " + ANDROID_SDK_SECONDARY + "

    ", - JOptionPane.QUESTION_MESSAGE); - - String[] options = new String[] { - "Download SDK automatically", "Locate SDK path manually" - }; - pane.setOptions(options); - - // highlight the safest option ala apple hig - pane.setInitialValue(options[0]); - - JDialog dialog = pane.createDialog(editor, null); - dialog.setVisible(true); - - Object result = pane.getValue(); - if (result == options[0]) { - return JOptionPane.YES_OPTION; - } else if (result == options[1]) { - return JOptionPane.NO_OPTION; - } else { - return JOptionPane.CLOSED_OPTION; - } - } - - // this was banished from Base because it encourages bad practice. - // TODO figure out a better way to handle the above. - static public File selectFolder(String prompt, File folder, Frame frame) { - if (Base.isMacOS()) { - if (frame == null) frame = new Frame(); //.pack(); - FileDialog fd = new FileDialog(frame, prompt, FileDialog.LOAD); - if (folder != null) { - fd.setDirectory(folder.getParent()); - //fd.setFile(folder.getName()); - } - System.setProperty("apple.awt.fileDialogForDirectories", "true"); - fd.setVisible(true); - System.setProperty("apple.awt.fileDialogForDirectories", "false"); - if (fd.getFile() == null) { - return null; - } - return new File(fd.getDirectory(), fd.getFile()); - - } else { - JFileChooser fc = new JFileChooser(); - fc.setDialogTitle(prompt); - if (folder != null) { - fc.setSelectedFile(folder); - } - fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); - - int returned = fc.showOpenDialog(frame); - if (returned == JFileChooser.APPROVE_OPTION) { - return fc.getSelectedFile(); - } - } - return null; - } - - - private static final String ADB_DAEMON_MSG_1 = "daemon not running"; - private static final String ADB_DAEMON_MSG_2 = "daemon started successfully"; - - public static ProcessResult runADB(final String... cmd) - throws InterruptedException, IOException { - final String[] adbCmd; - if (!cmd[0].equals("adb")) { - adbCmd = PApplet.splice(cmd, "adb", 0); - } else { - adbCmd = cmd; - } - // printing this here to see if anyone else is killing the adb server - if (processing.app.Base.DEBUG) { - PApplet.printArray(adbCmd); - } -// try { - ProcessResult adbResult = new ProcessHelper(adbCmd).execute(); - // Ignore messages about starting up an adb daemon - String out = adbResult.getStdout(); - if (out.contains(ADB_DAEMON_MSG_1) && out.contains(ADB_DAEMON_MSG_2)) { - StringBuilder sb = new StringBuilder(); - for (String line : out.split("\n")) { - if (!out.contains(ADB_DAEMON_MSG_1) && - !out.contains(ADB_DAEMON_MSG_2)) { - sb.append(line).append("\n"); - } - } - return new ProcessResult(adbResult.getCmd(), - adbResult.getResult(), - sb.toString(), - adbResult.getStderr(), - adbResult.getTime()); - } - return adbResult; -// } catch (IOException ioe) { -// ioe.printStackTrace(); -// throw ioe; -// } - } - - public static class SDKTarget { - public int version = 0; - public String name; - } - - public ArrayList getAvailableSdkTargets() throws IOException { - ArrayList targets = new ArrayList(); - - for(File platform : platforms.listFiles()) { - File propFile = new File(platform, "build.prop"); - if (!propFile.exists()) continue; - - SDKTarget target = new SDKTarget(); - - BufferedReader br = new BufferedReader(new FileReader(propFile)); - String line; - while ((line = br.readLine()) != null) { - String[] lineData = line.split("="); - if (lineData[0].equals("ro.build.version.sdk")) { - target.version = Integer.valueOf(lineData[1]); - } - - if (lineData[0].equals("ro.build.version.release")) { - target.name = lineData[1]; - break; - } - } - br.close(); - - if (target.version != 0 && target.name != null) targets.add(target); - } - - return targets; - } -} diff --git a/src/processing/mode/android/AndroidToolbar.java b/src/processing/mode/android/AndroidToolbar.java deleted file mode 100644 index b1c842b0e..000000000 --- a/src/processing/mode/android/AndroidToolbar.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - - Part of the Processing project - http://processing.org - - Copyright (c) 2011-12 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -package processing.mode.android; - -import java.awt.Image; -import java.awt.event.MouseEvent; - -import javax.swing.JPopupMenu; - -import processing.app.Base; -import processing.app.Editor; -import processing.app.EditorToolbar; - - -@SuppressWarnings("serial") -public class AndroidToolbar extends EditorToolbar { - static protected final int RUN = 0; - static protected final int STOP = 1; - - static protected final int NEW = 2; - static protected final int OPEN = 3; - static protected final int SAVE = 4; - static protected final int EXPORT = 5; - - - public AndroidToolbar(Editor editor, Base base) { - super(editor, base); - } - - - public void init() { - Image[][] images = loadImages(); - for (int i = 0; i < 6; i++) { - addButton(getTitle(i, false), getTitle(i, true), images[i], i == NEW); - } - } - - - static public String getTitle(int index, boolean shift) { - switch (index) { - case RUN: return !shift ? "Run on Device" : "Run in Emulator"; - case STOP: return "Stop"; - case NEW: return "New"; - case OPEN: return "Open"; - case SAVE: return "Save"; - case EXPORT: return !shift ? "Export Signed Package" : "Export Android Project"; - } - return null; - } - - - public void handlePressed(MouseEvent e, int sel) { - boolean shift = e.isShiftDown(); - AndroidEditor aeditor = (AndroidEditor) editor; - - switch (sel) { - case RUN: - if (!shift) { - aeditor.handleRunDevice(); - } else { - aeditor.handleRunEmulator(); - } - break; - - case STOP: - aeditor.handleStop(); - break; - - case OPEN: - // TODO I think we need a longer chain of accessors here. - JPopupMenu popup = editor.getMode().getToolbarMenu().getPopupMenu(); - popup.show(this, e.getX(), e.getY()); - break; - - case NEW: -// if (shift) { - base.handleNew(); -// } else { -// base.handleNewReplace(); -// } - break; - - case SAVE: - aeditor.handleSave(false); - break; - - case EXPORT: - if (!shift) { - aeditor.handleExportPackage(); - } else { - aeditor.handleExportProject(); - } - break; - } - } -} \ No newline at end of file diff --git a/src/processing/mode/android/BadSDKException.java b/src/processing/mode/android/BadSDKException.java deleted file mode 100644 index 0cd3feb01..000000000 --- a/src/processing/mode/android/BadSDKException.java +++ /dev/null @@ -1,8 +0,0 @@ -package processing.mode.android; - -@SuppressWarnings("serial") -public class BadSDKException extends Exception { - public BadSDKException(final String message) { - super(message); - } -} diff --git a/src/processing/mode/android/DeviceListener.java b/src/processing/mode/android/DeviceListener.java deleted file mode 100644 index 1548a2031..000000000 --- a/src/processing/mode/android/DeviceListener.java +++ /dev/null @@ -1,9 +0,0 @@ -package processing.mode.android; - -import java.util.List; - -public interface DeviceListener { - void stackTrace(final List trace); - - void sketchStopped(); -} diff --git a/src/processing/mode/android/Manifest.java b/src/processing/mode/android/Manifest.java deleted file mode 100644 index 903a9a1df..000000000 --- a/src/processing/mode/android/Manifest.java +++ /dev/null @@ -1,306 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2010-11 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -package processing.mode.android; - -import org.xml.sax.SAXException; -import processing.app.Base; -import processing.app.Sketch; -import processing.core.PApplet; -import processing.data.XML; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; - - -public class Manifest { - static final String MANIFEST_XML = "AndroidManifest.xml"; - - static final String WORLD_OF_HURT_COMING = - "Errors occurred while reading or writing " + MANIFEST_XML + ",\n" + - "which means lots of things are likely to stop working properly.\n" + - "To prevent losing any data, it's recommended that you use “Save As”\n" + - "to save a separate copy of your sketch, and the restart Processing."; - static final String MULTIPLE_ACTIVITIES = - "Processing only supports a single Activity in the AndroidManifest.xml\n" + - "file. Only the first activity entry will be updated, and you better \n" + - "hope that's the right one, smartypants."; - -// private Editor editor; - private Sketch sketch; - - // entries we care about from the manifest file -// private String packageName; - - /** the manifest data read from the file */ - private XML xml; - - -// public Manifest(Editor editor) { -// this.editor = editor; -// this.sketch = editor.getSketch(); -// load(); -// } - public Manifest(Sketch sketch) { - this.sketch = sketch; - load(); - } - - - private String defaultPackageName() { -// Sketch sketch = editor.getSketch(); - return AndroidBuild.basePackage + "." + sketch.getName().toLowerCase(); - } - - - // called by other classes who want an actual package name - // internally, we'll figure this out ourselves whether it's filled or not - public String getPackageName() { - String pkg = xml.getString("package"); - return pkg.length() == 0 ? defaultPackageName() : pkg; - } - - - public void setPackageName(String packageName) { -// this.packageName = packageName; - // this is the package attribute in the root object - xml.setString("package", packageName); - save(); - } - - public void setSdkTarget(String version) { - XML usesSdk = xml.getChild("uses-sdk"); - usesSdk.setString("android:minSdkVersion", version); - save(); - } - -//writer.println(" "); -//writer.println(" "); - static final String PERMISSION_PREFIX = "android.permission."; - - public String[] getPermissions() { - XML[] elements = xml.getChildren("uses-permission"); - int count = elements.length; - String[] names = new String[count]; - for (int i = 0; i < count; i++) { - names[i] = elements[i].getString("android:name").substring(PERMISSION_PREFIX.length()); - } - return names; - } - - - public void setPermissions(String[] names) { - // just remove all the old ones - for (XML kid : xml.getChildren("uses-permission")) { - xml.removeChild(kid); - } - // ...and add the new kids back - for (String name : names) { -// PNode newbie = new PNodeXML("uses-permission"); -// newbie.setString("android:name", PERMISSION_PREFIX + name); -// xml.addChild(newbie); - XML newbie = xml.addChild("uses-permission"); - newbie.setString("android:name", PERMISSION_PREFIX + name); - } - save(); - } - - - public void setClassName(String className) { - XML[] kids = xml.getChildren("application/activity"); - if (kids.length != 1) { - Base.showWarning("Don't touch that", MULTIPLE_ACTIVITIES, null); - } - XML activity = kids[0]; - String currentName = activity.getString("android:name"); - // only update if there are changes - if (currentName == null || !currentName.equals(className)) { - activity.setString("android:name", "." + className); - save(); - } - } - - - private void writeBlankManifest(final File file) { - final PrintWriter writer = PApplet.createWriter(file); - writer.println(""); - writer.println(""); - - // for now including this... we're wiring to a particular SDK version anyway... - writer.println(" "); -// writer.println(" "); // insert sdk version -// writer.println(" "); - - // turns out label is not required for the activity, so nixing it -// writer.println(" "); // pretty name -// writer.println(" android:label=\"\">"); - - // activity/android:name should be the full name (package + class name) of - // the actual activity class. or the package can be replaced by a single - // dot as a prefix as an easier shorthand. - writer.println(" "); - - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(" "); - writer.println(""); - writer.flush(); - writer.close(); - } - - - /** - * Save a new version of the manifest info to the build location. - * Also fill in any missing attributes that aren't yet set properly. - */ - protected void writeBuild(File file, String className, - boolean debug) throws IOException { - // write a copy to the build location - save(file); - - // load the copy from the build location and start messing with it - XML mf = null; - try { - mf = new XML(file); - - // package name, or default - String p = mf.getString("package").trim(); - if (p.length() == 0) { - mf.setString("package", defaultPackageName()); - } - - // app name and label, or the class name - XML app = mf.getChild("application"); - String label = app.getString("android:label"); - if (label.length() == 0) { - app.setString("android:label", className); - } - app.setString("android:debuggable", debug ? "true" : "false"); - - XML activity = app.getChild("activity"); - // the '.' prefix is just an alias for the full package name - // http://developer.android.com/guide/topics/manifest/activity-element.html#name - activity.setString("android:name", "." + className); // this has to be right - - PrintWriter writer = PApplet.createWriter(file); - writer.print(mf.toString()); - writer.flush(); -// mf.write(writer); - writer.close(); - - } catch (Exception e) { - e.printStackTrace(); - } - } - - - protected void load() { -// Sketch sketch = editor.getSketch(); -// File manifestFile = new File(sketch.getFolder(), MANIFEST_XML); -// XMLElement xml = null; - File manifestFile = getManifestFile(); - if (manifestFile.exists()) { - try { - xml = new XML(manifestFile); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Problem reading AndroidManifest.xml, creating a new version"); - - // remove the old manifest file, rename it with date stamp - long lastModified = manifestFile.lastModified(); - String stamp = AndroidMode.getDateStamp(lastModified); - File dest = new File(sketch.getFolder(), MANIFEST_XML + "." + stamp); - boolean moved = manifestFile.renameTo(dest); - if (!moved) { - System.err.println("Could not move/rename " + manifestFile.getAbsolutePath()); - System.err.println("You'll have to move or remove it before continuing."); - return; - } - } - } - if (xml == null) { - writeBlankManifest(manifestFile); - try { - xml = new XML(manifestFile); - } catch (FileNotFoundException e) { - System.err.println("Could not read " + manifestFile.getAbsolutePath()); - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } - } - if (xml == null) { - Base.showWarning("Error handling " + MANIFEST_XML, WORLD_OF_HURT_COMING, null); - } -// return xml; - } - - - protected void save() { - save(getManifestFile()); - } - - - /** - * Save to the sketch folder, so that it can be copied in later. - */ - protected void save(File file) { - PrintWriter writer = PApplet.createWriter(file); -// xml.write(writer); - writer.print(xml.toString()); - writer.flush(); - writer.close(); - } - - - private File getManifestFile() { - return new File(sketch.getFolder(), MANIFEST_XML); - } -} \ No newline at end of file diff --git a/src/processing/mode/android/Permissions.java b/src/processing/mode/android/Permissions.java deleted file mode 100644 index bca7cfc31..000000000 --- a/src/processing/mode/android/Permissions.java +++ /dev/null @@ -1,567 +0,0 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Part of the Processing project - http://processing.org - - Copyright (c) 2010 Ben Fry and Casey Reas - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License version 2 - as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -package processing.mode.android; - -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.event.*; -import java.util.ArrayList; -import java.util.HashMap; - -import javax.swing.*; -import javax.swing.border.*; -import javax.swing.event.*; - -import processing.app.Base; -import processing.app.Preferences; -import processing.app.Sketch; -import processing.app.Toolkit; - - -@SuppressWarnings("serial") -public class Permissions extends JFrame { - static final String GUIDE_URL = - "http://developer.android.com/guide/topics/security/security.html#permissions"; - - static final int BORDER_HORIZ = 5; - static final int BORDER_VERT = 3; - - JScrollPane permissionScroller; - JList permissionList; - JLabel descriptionLabel; -// JTextArea descriptionLabel; - -// Editor editor; - Sketch sketch; - - - public Permissions(Sketch sketch) { - //public Permissions(Editor editor) { - super("Android Permissions Selector"); - this.sketch = sketch; -// this.editor = editor; - -// XMLElement xml = - - permissionList = new CheckBoxList(); -// permissionList.addMouseListener(new MouseAdapter() { -// public void mousePressed(MouseEvent e) { -// if (isEnabled()) { -// int index = permissionList.locationToIndex(e.getPoint()); -// if (index == -1) { -// descriptionLabel.setText(""); -// } else { -//// descriptionLabel.setText("" + description[index] + ""); -// descriptionLabel.setText(description[index]); -// } -// } -// } -// }); - -// ListSelectionModel lsm = permissionList.getSelectionModel(); -// lsm.addListSelectionListener(new ListSelectionListener() { -// public void valueChanged(ListSelectionEvent e) { -//// ListSelectionModel lsm = (ListSelectionModel) e.getSource(); -// int index = e.getFirstIndex(); -// if (index == -1) { -// descriptionLabel.setText(""); -// } else { -// descriptionLabel.setText("" + description[index] + ""); -//// descriptionLabel.setText(description[index]); -// } -// } -// }); - permissionList.addListSelectionListener(new ListSelectionListener() { - public void valueChanged(ListSelectionEvent e) { - if (e.getValueIsAdjusting() == false) { - int index = permissionList.getSelectedIndex(); - if (index == -1) { - descriptionLabel.setText(""); - } else { - descriptionLabel.setText("" + description[index] + ""); - //descriptionLabel.setText(description[index]); - } - } - } - }); -// permissionList.setLayoutOrientation(JList.HORIZONTAL_WRAP); -// permissionList.setFixedCellWidth(300); -// int h = permissionList.getFixedCellHeight(); -// permissionList.setFixedCellHeight(h + 8); - permissionList.setFixedCellHeight(20); - permissionList.setBorder(new EmptyBorder(BORDER_VERT, BORDER_HORIZ, - BORDER_VERT, BORDER_HORIZ)); - - DefaultListModel model = new DefaultListModel(); - permissionList.setModel(model); - for (String item : title) { - model.addElement(new JCheckBox(item)); - } - - permissionScroller = - new JScrollPane(permissionList, - ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, - ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); -// permissionList.setVisibleRowCount(20); - permissionList.setVisibleRowCount(12); -// permissionList.setPreferredSize(new Dimension(400, 300)); -// permissionsScroller.setPreferredSize(new Dimension(400, 300)); - permissionList.addKeyListener(new KeyAdapter() { - public void keyTyped(KeyEvent e) { - if (e.getKeyChar() == ' ') { - int index = permissionList.getSelectedIndex(); - JCheckBox checkbox = - permissionList.getModel().getElementAt(index); - checkbox.setSelected(!checkbox.isSelected()); - permissionList.repaint(); - } - } - }); - - Container outer = getContentPane(); -// outer.setLayout(new BorderLayout()); - -// JPanel pain = new JPanel(); - Box pain = Box.createVerticalBox(); - pain.setBorder(new EmptyBorder(13, 13, 13, 13)); -// outer.add(pain, BorderLayout.CENTER); - outer.add(pain); -// pain.setLayout(new BoxLayout(pain, BoxLayout.Y_AXIS)); - - String labelText = - "" + - "Android applications must specifically ask for permission\n" + - "to do things like connect to the internet, write a file,\n" + - "or make phone calls. When installing your application,\n" + - "users will be asked whether they want to allow such access.\n" + - "More about permissions can be found " + - "here."; -// "" + -// "Android applications must specifically ask for permission\n" + -// "to do things like connect to the internet, write a file,\n" + -// "or make phone calls. When installing your application,\n" + -// "users will be asked whether they want to allow such access.\n" + -// "More about permissions can be found " + -// "here."; -// JTextArea textarea = new JTextArea(labelText); -// JTextArea textarea = new JTextArea(5, 40); -// textarea.setText(labelText); - JLabel textarea = new JLabel(labelText); -// JLabel textarea = new JLabel(labelText) { -// public Dimension getPreferredSize() { -// return new Dimension(400, 100); -// } -// public Dimension getMinimumSize() { -// return getPreferredSize(); -// } -// public Dimension getMaximumSize() { -// return getPreferredSize(); -// } -// }; - textarea.setPreferredSize(new Dimension(400, 100)); - textarea.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - Base.openURL(GUIDE_URL); - } - }); - //textarea.setHorizontalAlignment(SwingConstants.LEFT); - textarea.setAlignmentX(LEFT_ALIGNMENT); - -// textarea.setBorder(new EmptyBorder(13, 8, 13, 8)); - -// textarea.setBackground(null); -// textarea.setBackground(Color.RED); -// textarea.setEditable(false); -// textarea.setHighlighter(null); -// textarea.setFont(new Font("Dialog", Font.PLAIN, 12)); - pain.add(textarea); -// textarea.setForeground(Color.RED); -// pain.setBackground(Color.GREEN); - -// permissionList.setEnabled(false); - - permissionScroller.setAlignmentX(LEFT_ALIGNMENT); - pain.add(permissionScroller); -// pain.add(permissionList); - pain.add(Box.createVerticalStrut(8)); - -// descriptionLabel = new JTextArea(4, 10); - descriptionLabel = new JLabel(); -// descriptionLabel = new JLabel() { -// public Dimension getPreferredSize() { -// return new Dimension(400, 100); -// } -// public Dimension getMinimumSize() { -// return new Dimension(400, 100); -// } -// public Dimension getMaximumSize() { -// return new Dimension(400, 100); -// } -// }; - descriptionLabel.setPreferredSize(new Dimension(400, 50)); - descriptionLabel.setVerticalAlignment(SwingConstants.TOP); - descriptionLabel.setAlignmentX(LEFT_ALIGNMENT); - pain.add(descriptionLabel); - pain.add(Box.createVerticalStrut(8)); - - JPanel buttons = new JPanel(); -// buttons.setPreferredSize(new Dimension(400, 35)); -// JPanel buttons = new JPanel() { -// public Dimension getPreferredSize() { -// return new Dimension(400, 35); -// } -// public Dimension getMinimumSize() { -// return new Dimension(400, 35); -// } -// public Dimension getMaximumSize() { -// return new Dimension(400, 35); -// } -// }; - -// Box buttons = Box.createHorizontalBox(); - buttons.setAlignmentX(LEFT_ALIGNMENT); - JButton okButton = new JButton("OK"); - Dimension dim = new Dimension(Preferences.BUTTON_WIDTH, - okButton.getPreferredSize().height); - okButton.setPreferredSize(dim); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - //PApplet.println(getSelections()); - saveSelections(); - setVisible(false); - } - }); - okButton.setEnabled(true); - - JButton cancelButton = new JButton("Cancel"); - cancelButton.setPreferredSize(dim); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); - cancelButton.setEnabled(true); - - // think different, biznatchios! - if (Base.isMacOS()) { - buttons.add(cancelButton); -// buttons.add(Box.createHorizontalStrut(8)); - buttons.add(okButton); - } else { - buttons.add(okButton); -// buttons.add(Box.createHorizontalStrut(8)); - buttons.add(cancelButton); - } -// buttons.setMaximumSize(new Dimension(300, buttons.getPreferredSize().height)); - pain.add(buttons); - - JRootPane root = getRootPane(); - root.setDefaultButton(okButton); - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - setVisible(false); - } - }; - Toolkit.registerWindowCloseKeys(root, disposer); - Toolkit.setIcon(this); - - pack(); - - Dimension screen = Toolkit.getScreenSize(); - Dimension windowSize = getSize(); - - setLocation((screen.width - windowSize.width) / 2, - (screen.height - windowSize.height) / 2); - - Manifest mf = new Manifest(sketch); - setSelections(mf.getPermissions()); - - // show the window and get to work - setVisible(true); - } - - - @SuppressWarnings("rawtypes") - protected void setSelections(String[] sel) { -// processing.core.PApplet.println("permissions are:"); -// processing.core.PApplet.println(sel); - HashMap map = new HashMap(); - for (String s : sel) { - map.put(s, new Object()); - } - DefaultListModel model = (DefaultListModel) permissionList.getModel(); - for (int i = 0; i < count; i++) { - JCheckBox box = (JCheckBox) model.get(i); -// System.out.println(map.containsKey(box.getText()) + " " + box.getText()); - box.setSelected(map.containsKey(box.getText())); - } - } - - - @SuppressWarnings("rawtypes") - protected String[] getSelections() { - ArrayList sel = new ArrayList(); - DefaultListModel model = (DefaultListModel) permissionList.getModel(); - for (int i = 0; i < count; i++) { - if (((JCheckBox) model.get(i)).isSelected()) { - sel.add(title[i]); - } - } - return sel.toArray(new String[0]); - } - - - protected void saveSelections() { - String[] sel = getSelections(); - Manifest mf = new Manifest(sketch); - mf.setPermissions(sel); - } - - - public String getMenuTitle() { - return "Android Permissions"; - } - - -// public void init(Editor editor) { -// this.editor = editor; -// } - - -// public void run() { -// // parse the manifest file here and figure out what permissions are set -// Manifest mf = new Manifest(editor); -// setSelections(mf.getPermissions()); -// -// // show the window and get to work -// setVisible(true); -// } - - - /** - * Created by inserting the HTML doc into OpenOffice, then copy and pasting - * the table into a plain text document, then adding the quotes via search - * and replace. If there's a way to auto-create from aapt, that'd be better, - * but I haven't found anything yet. - */ - static final String[] listing = { - "ACCESS_CHECKIN_PROPERTIES", "Allows read/write access to the \"properties\" table in the checkin database, to change values that get uploaded.", - "ACCESS_COARSE_LOCATION", "Allows an application to access coarse (e.g., Cell-ID, WiFi) location", - "ACCESS_FINE_LOCATION", "Allows an application to access fine (e.g., GPS) location", - "ACCESS_LOCATION_EXTRA_COMMANDS", "Allows an application to access extra location provider commands", - "ACCESS_MOCK_LOCATION", "Allows an application to create mock location providers for testing", - "ACCESS_NETWORK_STATE", "Allows applications to access information about networks", - "ACCESS_SURFACE_FLINGER", "Allows an application to use SurfaceFlinger's low level features", - "ACCESS_WIFI_STATE", "Allows applications to access information about Wi-Fi networks", - "ACCOUNT_MANAGER", "Allows applications to call into AccountAuthenticators.", - "AUTHENTICATE_ACCOUNTS", "Allows an application to act as an AccountAuthenticator for the AccountManager", - "BATTERY_STATS", "Allows an application to collect battery statistics", - "BIND_APPWIDGET", "Allows an application to tell the AppWidget service which application can access AppWidget's data.", - "BIND_DEVICE_ADMIN", "Must be required by device administration receiver, to ensure that only the system can interact with it.", - "BIND_INPUT_METHOD", "Must be required by an InputMethodService, to ensure that only the system can bind to it.", - "BIND_WALLPAPER", "Must be required by a WallpaperService, to ensure that only the system can bind to it.", - "BLUETOOTH", "Allows applications to connect to paired bluetooth devices", - "BLUETOOTH_ADMIN", "Allows applications to discover and pair bluetooth devices", - "BRICK", "Required to be able to disable the device (very dangerous!).", - "BROADCAST_PACKAGE_REMOVED", "Allows an application to broadcast a notification that an application package has been removed.", - "BROADCAST_SMS", "Allows an application to broadcast an SMS receipt notification", - "BROADCAST_STICKY", "Allows an application to broadcast sticky intents.", - "BROADCAST_WAP_PUSH", "Allows an application to broadcast a WAP PUSH receipt notification", - "CALL_PHONE", "Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call being placed.", - "CALL_PRIVILEGED", "Allows an application to call any phone number, including emergency numbers, without going through the Dialer user interface for the user to confirm the call being placed.", - "CAMERA", "Required to be able to access the camera device.", - "CHANGE_COMPONENT_ENABLED_STATE", "Allows an application to change whether an application component (other than its own) is enabled or not.", - "CHANGE_CONFIGURATION", "Allows an application to modify the current configuration, such as locale.", - "CHANGE_NETWORK_STATE", "Allows applications to change network connectivity state", - "CHANGE_WIFI_MULTICAST_STATE", "Allows applications to enter Wi-Fi Multicast mode", - "CHANGE_WIFI_STATE", "Allows applications to change Wi-Fi connectivity state", - "CLEAR_APP_CACHE", "Allows an application to clear the caches of all installed applications on the device.", - "CLEAR_APP_USER_DATA", "Allows an application to clear user data", - "CONTROL_LOCATION_UPDATES", "Allows enabling/disabling location update notifications from the radio.", - "DELETE_CACHE_FILES", "Allows an application to delete cache files.", - "DELETE_PACKAGES", "Allows an application to delete packages.", - "DEVICE_POWER", "Allows low-level access to power management", - "DIAGNOSTIC", "Allows applications to RW to diagnostic resources.", - "DISABLE_KEYGUARD", "Allows applications to disable the keyguard", - "DUMP", "Allows an application to retrieve state dump information from system services.", - "EXPAND_STATUS_BAR", "Allows an application to expand or collapse the status bar.", - "FACTORY_TEST", "Run as a manufacturer test application, running as the root user.", - "FLASHLIGHT", "Allows access to the flashlight", - "FORCE_BACK", "Allows an application to force a BACK operation on whatever is the top activity.", - "GET_ACCOUNTS", "Allows access to the list of accounts in the Accounts Service", - "GET_PACKAGE_SIZE", "Allows an application to find out the space used by any package.", - "GET_TASKS", "Allows an application to get information about the currently or recently running tasks: a thumbnail representation of the tasks, what activities are running in it, etc.", - "GLOBAL_SEARCH", "This permission can be used on content providers to allow the global search system to access their data.", - "HARDWARE_TEST", "Allows access to hardware peripherals.", - "INJECT_EVENTS", "Allows an application to inject user events (keys, touch, trackball) into the event stream and deliver them to ANY window.", - "INSTALL_LOCATION_PROVIDER", "Allows an application to install a location provider into the Location Manager", - "INSTALL_PACKAGES", "Allows an application to install packages.", - "INTERNAL_SYSTEM_WINDOW", "Allows an application to open windows that are for use by parts of the system user interface.", - "INTERNET", "Allows applications to open network sockets.", - "KILL_BACKGROUND_PROCESSES", "Allows an application to call killBackgroundProcesses(String).", - "MANAGE_ACCOUNTS", "Allows an application to manage the list of accounts in the AccountManager", - "MANAGE_APP_TOKENS", "Allows an application to manage (create, destroy, Z-order) application tokens in the window manager.", - "MASTER_CLEAR", "", - "MODIFY_AUDIO_SETTINGS", "Allows an application to modify global audio settings", - "MODIFY_PHONE_STATE", "Allows modification of the telephony state - power on, mmi, etc.", - "MOUNT_FORMAT_FILESYSTEMS", "Allows formatting file systems for removable storage.", - "MOUNT_UNMOUNT_FILESYSTEMS", "Allows mounting and unmounting file systems for removable storage.", - "PERSISTENT_ACTIVITY", "Allow an application to make its activities persistent.", - "PROCESS_OUTGOING_CALLS", "Allows an application to monitor, modify, or abort outgoing calls.", - "READ_CALENDAR", "Allows an application to read the user's calendar data.", - "READ_CONTACTS", "Allows an application to read the user's contacts data.", - "READ_FRAME_BUFFER", "Allows an application to take screen shots and more generally get access to the frame buffer data", - "READ_HISTORY_BOOKMARKS", "Allows an application to read (but not write) the user's browsing history and bookmarks.", - "READ_INPUT_STATE", "Allows an application to retrieve the current state of keys and switches.", - "READ_LOGS", "Allows an application to read the low-level system log files.", - "READ_OWNER_DATA", "Allows an application to read the owner's data.", - "READ_PHONE_STATE", "Allows read only access to phone state.", - "READ_SMS", "Allows an application to read SMS messages.", - "READ_SYNC_SETTINGS", "Allows applications to read the sync settings", - "READ_SYNC_STATS", "Allows applications to read the sync stats", - "REBOOT", "Required to be able to reboot the device.", - "RECEIVE_BOOT_COMPLETED", "Allows an application to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting.", - "RECEIVE_MMS", "Allows an application to monitor incoming MMS messages, to record or perform processing on them.", - "RECEIVE_SMS", "Allows an application to monitor incoming SMS messages, to record or perform processing on them.", - "RECEIVE_WAP_PUSH", "Allows an application to monitor incoming WAP push messages.", - "RECORD_AUDIO", "Allows an application to record audio", - "REORDER_TASKS", "Allows an application to change the Z-order of tasks", - "RESTART_PACKAGES", "This constant is deprecated. The restartPackage(String) API is no longer supported. ", - "SEND_SMS", "Allows an application to send SMS messages.", - "SET_ACTIVITY_WATCHER", "Allows an application to watch and control how activities are started globally in the system.", - "SET_ALWAYS_FINISH", "Allows an application to control whether activities are immediately finished when put in the background.", - "SET_ANIMATION_SCALE", "Modify the global animation scaling factor.", - "SET_DEBUG_APP", "Configure an application for debugging.", - "SET_ORIENTATION", "Allows low-level access to setting the orientation (actually rotation) of the screen.", - "SET_PREFERRED_APPLICATIONS", "This constant is deprecated. No longer useful, see addPackageToPreferred(String) for details. ", - "SET_PROCESS_LIMIT", "Allows an application to set the maximum number of (not needed) application processes that can be running.", - "SET_TIME", "Allows applications to set the system time", - "SET_TIME_ZONE", "Allows applications to set the system time zone", - "SET_WALLPAPER", "Allows applications to set the wallpaper", - "SET_WALLPAPER_HINTS", "Allows applications to set the wallpaper hints", - "SIGNAL_PERSISTENT_PROCESSES", "Allow an application to request that a signal be sent to all persistent processes", - "STATUS_BAR", "Allows an application to open, close, or disable the status bar and its icons.", - "SUBSCRIBED_FEEDS_READ", "Allows an application to allow access the subscribed feeds ContentProvider.", - "SUBSCRIBED_FEEDS_WRITE", "", - "SYSTEM_ALERT_WINDOW", "Allows an application to open windows using the type TYPE_SYSTEM_ALERT, shown on top of all other applications.", - "UPDATE_DEVICE_STATS", "Allows an application to update device statistics.", - "USE_CREDENTIALS", "Allows an application to request authtokens from the AccountManager", - "VIBRATE", "Allows access to the vibrator", - "WAKE_LOCK", "Allows using PowerManager WakeLocks to keep processor from sleeping or screen from dimming", - "WRITE_APN_SETTINGS", "Allows applications to write the apn settings", - "WRITE_CALENDAR", "Allows an application to write (but not read) the user's calendar data.", - "WRITE_CONTACTS", "Allows an application to write (but not read) the user's contacts data.", - "WRITE_EXTERNAL_STORAGE", "Allows an application to write to external storage", - "WRITE_GSERVICES", "Allows an application to modify the Google service map.", - "WRITE_HISTORY_BOOKMARKS", "Allows an application to write (but not read) the user's browsing history and bookmarks.", - "WRITE_OWNER_DATA", "Allows an application to write (but not read) the owner's data.", - "WRITE_SECURE_SETTINGS", "Allows an application to read or write the secure system settings.", - "WRITE_SETTINGS", "Allows an application to read or write the system settings.", - "WRITE_SMS", "Allows an application to write SMS messages.", - "WRITE_SYNC_SETTINGS", "Allows applications to write the sync settings" - }; - - static String[] title; - static String[] description; - static int count; - static { - count = listing.length / 2; - title = new String[count]; - description = new String[count]; - for (int i = 0; i < count; i++) { - title[i] = listing[i*2]; - description[i] = listing[i*2+1]; - } - } -} - - -// Code for this CheckBoxList class found on the net, though I've lost the -// link. If you run across the original version, please let me know so that -// the original author can be credited properly. It was from a snippet -// collection, but it seems to have been picked up so many places with others -// placing their copyright on it that I haven't been able to determine the -// original author. [fry 20100216] -@SuppressWarnings("serial") -class CheckBoxList extends JList { - protected static Border noFocusBorder = new EmptyBorder(1, 1, 1, 1); - int checkboxWidth; - - public CheckBoxList() { - setCellRenderer(new CellRenderer()); - - // get the width of a checkbox so we can figure out if the mouse is inside - checkboxWidth = new JCheckBox().getPreferredSize().width; - // add the amount for the inset - checkboxWidth += Permissions.BORDER_HORIZ; - - addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent e) { - if (isEnabled()) { -// System.out.println("cbw = " + checkboxWidth); - int index = locationToIndex(e.getPoint()); -// descriptionLabel.setText(description[index]); - if (index != -1) { - JCheckBox checkbox = getModel().getElementAt(index); - //System.out.println("mouse event in list: " + e); -// System.out.println(checkbox.getSize() + " ... " + checkbox); -// if (e.getX() < checkbox.getSize().height) { - if (e.getX() < checkboxWidth) { - checkbox.setSelected(!checkbox.isSelected()); - repaint(); - } - } - } - } - }); - setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - } - - - protected class CellRenderer implements ListCellRenderer { - public Component getListCellRendererComponent(JList list, - JCheckBox checkbox, - int index, boolean isSelected, - boolean cellHasFocus) { -// checkbox.setBorder(new EmptyBorder(13, 5, 3, 5)); // trying again - checkbox.setBackground(isSelected ? getSelectionBackground() : getBackground()); - checkbox.setForeground(isSelected ? getSelectionForeground() : getForeground()); - //checkbox.setEnabled(isEnabled()); - checkbox.setEnabled(list.isEnabled()); - checkbox.setFont(getFont()); - checkbox.setFocusPainted(false); - checkbox.setBorderPainted(true); - checkbox.setBorder(isSelected ? UIManager.getBorder("List.focusCellHighlightBorder") : noFocusBorder); - return checkbox; - } - -// @Override -// public Component getListCellRendererComponent(JList list, -// JCheckBox value, int index, -// boolean isSelected, -// boolean cellHasFocus) { -// // TODO Auto-generated method stub -// return null; -// } - } -} diff --git a/src/processing/mode/android/SDKDownloader.java b/src/processing/mode/android/SDKDownloader.java deleted file mode 100644 index 1904e0a0e..000000000 --- a/src/processing/mode/android/SDKDownloader.java +++ /dev/null @@ -1,391 +0,0 @@ -package processing.mode.android; - -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.SAXException; - -import processing.app.Base; -import processing.app.Preferences; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.*; -import java.net.URL; -import java.net.URLConnection; -import java.util.Enumeration; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -@SuppressWarnings("serial") -public class SDKDownloader extends JFrame implements PropertyChangeListener { - - private static final String URL_REPOSITORY = "https://dl-ssl.google.com/android/repository/repository-10.xml"; - private static final String URL_REPOSITORY_FOLDER = "http://dl-ssl.google.com/android/repository/"; - private static final String URL_USB_DRIVER = "https://dl-ssl.google.com//android/repository/latest_usb_driver_windows.zip"; - - private static final String PLATFORM_API_LEVEL = "10"; - - public static final String PROPERTY_CHANGE_EVENT_TOTAL = "total"; - private static final String PROPERTY_CHANGE_EVENT_DOWNLOADED = "downloaded"; - - private AndroidMode androidMode; - - JProgressBar progressBar; - JLabel downloadedTextArea; - - private int totalSize = 0; - private static ZipFile zip; - - class SDKUrlHolder { - public String platformToolsUrl, buildToolsUrl, platformUrl, toolsUrl; - public String platformToolsFilename, buildToolsFilename, platformFilename, toolsFilename; - public int totalSize = 0; - } - - class SDKDownloadTask extends SwingWorker { - - private int downloadedSize = 0; - private int BUFFER_SIZE = 4096; - - @Override - protected Object doInBackground() throws Exception { - String hostOs = getOsString(); - File modeFolder = new File(Base.getSketchbookModesFolder() + "/AndroidMode"); - - // creating sdk folders - File sdkFolder = new File(modeFolder, "sdk"); - if (!sdkFolder.exists()) sdkFolder.mkdir(); - File platformsFolder = new File(sdkFolder, "platforms"); - if (!platformsFolder.exists()) platformsFolder.mkdir(); - File buildToolsFolder = new File(sdkFolder, "build-tools"); - if (!buildToolsFolder.exists()) buildToolsFolder.mkdir(); - File extrasFolder = new File(sdkFolder, "extras"); - if(!extrasFolder.exists()) extrasFolder.mkdir(); - - // creating temp folder for downloaded zip packages - File tempFolder = new File(modeFolder, "temp"); - if (!tempFolder.exists()) tempFolder.mkdir(); - - try { - SDKUrlHolder downloadUrls = getDownloadUrls(URL_REPOSITORY, hostOs); - firePropertyChange(PROPERTY_CHANGE_EVENT_TOTAL, 0, downloadUrls.totalSize); - totalSize = downloadUrls.totalSize; - - // tools - File downloadedTools = new File(tempFolder, downloadUrls.toolsFilename); - downloadAndUnpack(downloadUrls.toolsUrl, downloadedTools, sdkFolder); - - // platform-tools - File downloadedPlatformTools = new File(tempFolder, downloadUrls.platformToolsFilename); - downloadAndUnpack(downloadUrls.platformToolsUrl, downloadedPlatformTools, sdkFolder); - - // build-tools - File downloadedBuildTools = new File(tempFolder, downloadUrls.buildToolsFilename); - downloadAndUnpack(downloadUrls.buildToolsUrl, downloadedBuildTools, buildToolsFolder); - - // platform - File downloadedPlatform = new File(tempFolder, downloadUrls.platformFilename); - downloadAndUnpack(downloadUrls.platformUrl, downloadedPlatform, platformsFolder); - - // usb driver - if(Base.isWindows()) { - File usbDriverFolder = new File(extrasFolder, "google"); - File downloadedFolder = new File(tempFolder, "latest_usb_driver_windows.zip"); - downloadAndUnpack(URL_USB_DRIVER, downloadedFolder, usbDriverFolder); - } - - if (Base.isLinux() || Base.isMacOS()) { - Runtime.getRuntime().exec("chmod -R 755 " + sdkFolder.getAbsolutePath()); - } - - tempFolder.delete(); - - Base.getPlatform().setenv("ANDROID_SDK", sdkFolder.getAbsolutePath()); - Preferences.set("android.sdk.path", sdkFolder.getAbsolutePath()); - androidMode.loadSDK(); - } catch (ParserConfigurationException e) { - // TODO Handle exceptions here somehow (ie show error message) and handle at least mkdir() results (above) - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void done() { - super.done(); - setVisible(false); - } - - private void downloadAndUnpack(String urlString, File saveTo, File unpackTo) throws IOException { - URL url = new URL(urlString); - URLConnection conn = url.openConnection(); - - InputStream inputStream = conn.getInputStream(); - FileOutputStream outputStream = new FileOutputStream(saveTo); - - byte[] b = new byte[BUFFER_SIZE]; - int count; - while ((count = inputStream.read(b)) >= 0) { - outputStream.write(b, 0, count); - downloadedSize += count; - - firePropertyChange(PROPERTY_CHANGE_EVENT_DOWNLOADED, 0, downloadedSize); - } - outputStream.flush(); outputStream.close(); inputStream.close(); - - inputStream.close(); - outputStream.close(); - - extractFolder(saveTo, unpackTo); - } - - private String getOsString() { - if (Base.isWindows()) { - return "windows"; - } else if (Base.isLinux()) { - return "linux"; - } else { - return "macosx"; - } - } - - private SDKUrlHolder getDownloadUrls(String repositoryUrl, String requiredHostOs) throws ParserConfigurationException, IOException, SAXException { - SDKUrlHolder urlHolder = new SDKUrlHolder(); - - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - Document doc = db.parse(new URL(repositoryUrl).openStream()); - - // platform - NodeList platformList = doc.getElementsByTagName("sdk:platform"); - for(int i = 0; i < platformList.getLength(); i++) { - Node platform = platformList.item(i); - if (((Element) platform).getElementsByTagName("sdk:api-level").item(0).getTextContent().equals(PLATFORM_API_LEVEL)) { - Node archiveListItem = ((Element) platform).getElementsByTagName("sdk:archives").item(0); - Node archiveItem = ((Element) archiveListItem).getElementsByTagName("sdk:archive").item(0); - urlHolder.platformUrl = ((Element) archiveItem).getElementsByTagName("sdk:url").item(0).getTextContent(); - urlHolder.platformFilename = urlHolder.platformUrl.split("/")[urlHolder.platformUrl.split("/").length-1]; - urlHolder.totalSize += Integer.parseInt(((Element) archiveItem).getElementsByTagName("sdk:size").item(0).getTextContent()); - } - } - - // platform-tools - Node platformToolItem = doc.getElementsByTagName("sdk:platform-tool").item(0); - Node archiveListItem = ((Element) platformToolItem).getElementsByTagName("sdk:archives").item(0); - NodeList archiveList = ((Element) archiveListItem).getElementsByTagName("sdk:archive"); - for(int i = 0; i < archiveList.getLength(); i++) { - Node archive = archiveList.item(i); - String hostOs = ((Element) archive).getElementsByTagName("sdk:host-os").item(0).getTextContent(); - if (hostOs.equals(requiredHostOs)) { - urlHolder.platformToolsFilename = (((Element) archive).getElementsByTagName("sdk:url").item(0).getTextContent()); - urlHolder.platformToolsUrl = URL_REPOSITORY_FOLDER + urlHolder.platformToolsFilename; - urlHolder.totalSize += Integer.parseInt(((Element) archive).getElementsByTagName("sdk:size").item(0).getTextContent()); - break; - } - } - - // build-tools - Node buildToolsItem = doc.getElementsByTagName("sdk:build-tool").item(doc.getElementsByTagName("sdk:build-tool").getLength()-1); - archiveListItem = ((Element) buildToolsItem).getElementsByTagName("sdk:archives").item(0); - archiveList = ((Element) archiveListItem).getElementsByTagName("sdk:archive"); - for(int i = 0; i < archiveList.getLength(); i++) { - Node archive = archiveList.item(i); - String hostOs = ((Element) archive).getElementsByTagName("sdk:host-os").item(0).getTextContent(); - if (hostOs.equals(requiredHostOs)) { - urlHolder.buildToolsFilename = (((Element) archive).getElementsByTagName("sdk:url").item(0).getTextContent()); - urlHolder.buildToolsUrl = URL_REPOSITORY_FOLDER + urlHolder.buildToolsFilename; - urlHolder.totalSize += Integer.parseInt(((Element) archive).getElementsByTagName("sdk:size").item(0).getTextContent()); - break; - } - } - - // tools - Node toolsItem = doc.getElementsByTagName("sdk:tool").item(0); - archiveListItem = ((Element) toolsItem).getElementsByTagName("sdk:archives").item(0); - archiveList = ((Element) archiveListItem).getElementsByTagName("sdk:archive"); - for(int i = 0; i < archiveList.getLength(); i++) { - Node archive = archiveList.item(i); - String hostOs = ((Element) archive).getElementsByTagName("sdk:host-os").item(0).getTextContent(); - if (hostOs.equals(requiredHostOs)) { - urlHolder.toolsFilename = (((Element) archive).getElementsByTagName("sdk:url").item(0).getTextContent()); - urlHolder.toolsUrl = URL_REPOSITORY_FOLDER + urlHolder.toolsFilename; - urlHolder.totalSize += Integer.parseInt(((Element) archive).getElementsByTagName("sdk:size").item(0).getTextContent()); - break; - } - } - - return urlHolder; - } - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals(PROPERTY_CHANGE_EVENT_TOTAL)) { - progressBar.setIndeterminate(false); - totalSize = (Integer) evt.getNewValue(); - progressBar.setMaximum(totalSize); - } else if (evt.getPropertyName().equals(PROPERTY_CHANGE_EVENT_DOWNLOADED)) { - downloadedTextArea.setText(humanReadableByteCount((Integer) evt.getNewValue(), true) - + " / " + humanReadableByteCount(totalSize, true)); - progressBar.setValue((Integer) evt.getNewValue()); - } - } - - // http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java - public static String humanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp-1) + (si ? "" : "i"); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - - public SDKDownloader(AndroidMode androidMode) { - super("Android SDK downloading..."); - - this.androidMode = androidMode; - - createLayout(); - } - - public void startDownload() { - SDKDownloadTask downloadTask = new SDKDownloadTask(); - downloadTask.addPropertyChangeListener(this); - downloadTask.execute(); - } - - private void createLayout() { - Container outer = getContentPane(); - outer.removeAll(); - - Box pain = Box.createVerticalBox(); - pain.setBorder(new EmptyBorder(13, 13, 13, 13)); - outer.add(pain); - - String labelText = - "Downloading Android SDK..."; - JLabel textarea = new JLabel(labelText); - textarea.setAlignmentX(LEFT_ALIGNMENT); - pain.add(textarea); - - progressBar = new JProgressBar(0, 100); - progressBar.setValue(0); - progressBar.setStringPainted(true); - progressBar.setIndeterminate(true); - progressBar.setBorder(new EmptyBorder(10, 10, 10, 10) ); - pain.add(progressBar); - - downloadedTextArea = new JLabel(""); - downloadedTextArea.setAlignmentX(LEFT_ALIGNMENT); - pain.add(downloadedTextArea); - - // buttons - JPanel buttons = new JPanel(); -// buttons.setPreferredSize(new Dimension(400, 35)); -// JPanel buttons = new JPanel() { -// public Dimension getPreferredSize() { -// return new Dimension(400, 35); -// } -// public Dimension getMinimumSize() { -// return new Dimension(400, 35); -// } -// public Dimension getMaximumSize() { -// return new Dimension(400, 35); -// } -// }; - -// Box buttons = Box.createHorizontalBox(); - buttons.setAlignmentX(LEFT_ALIGNMENT); - JButton cancelButton = new JButton("Cancel download"); - Dimension dim = new Dimension(Preferences.BUTTON_WIDTH*2, - cancelButton.getPreferredSize().height); - - cancelButton.setPreferredSize(dim); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - setVisible(false); - } - }); - cancelButton.setEnabled(true); - - buttons.add(cancelButton); -// buttons.setMaximumSize(new Dimension(300, buttons.getPreferredSize().height)); - pain.add(buttons); - - JRootPane root = getRootPane(); - root.setDefaultButton(cancelButton); - ActionListener disposer = new ActionListener() { - public void actionPerformed(ActionEvent actionEvent) { - setVisible(false); - } - }; - processing.app.Toolkit.registerWindowCloseKeys(root, disposer); - processing.app.Toolkit.setIcon(this); - - pack(); - - Dimension screen = processing.app.Toolkit.getScreenSize(); - Dimension windowSize = getSize(); - - setLocation((screen.width - windowSize.width) / 2, - (screen.height - windowSize.height) / 2); - - setVisible(true); - setAlwaysOnTop(true); - } - - static public void extractFolder(File file, File newPath) throws IOException { - int BUFFER = 2048; - zip = new ZipFile(file); - Enumeration zipFileEntries = zip.entries(); - - // Process each entry - while (zipFileEntries.hasMoreElements()) { - // grab a zip file entry - ZipEntry entry = zipFileEntries.nextElement(); - String currentEntry = entry.getName(); - File destFile = new File(newPath, currentEntry); - //destFile = new File(newPath, destFile.getName()); - File destinationParent = destFile.getParentFile(); - - // create the parent directory structure if needed - destinationParent.mkdirs(); - - if (!entry.isDirectory()) { - BufferedInputStream is = new BufferedInputStream(zip - .getInputStream(entry)); - int currentByte; - // establish buffer for writing file - byte data[] = new byte[BUFFER]; - - // write the current file to disk - FileOutputStream fos = new FileOutputStream(destFile); - BufferedOutputStream dest = new BufferedOutputStream(fos, - BUFFER); - - // read and write until last byte is encountered - while ((currentByte = is.read(data, 0, BUFFER)) != -1) { - dest.write(data, 0, currentByte); - } - dest.flush(); - dest.close(); - is.close(); - } - } - } -} \ No newline at end of file diff --git a/src/processing/mode/android/signing/IApkSignatureProvider.java b/src/processing/mode/android/signing/IApkSignatureProvider.java deleted file mode 100644 index cd7cdc7fe..000000000 --- a/src/processing/mode/android/signing/IApkSignatureProvider.java +++ /dev/null @@ -1,14 +0,0 @@ -package processing.mode.android.signing; - -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.jar.JarOutputStream; - -public interface IApkSignatureProvider { - - public void writeSignatureBlock(byte[] message, String signatureAlgorithm, X509Certificate publicKey, - PrivateKey privateKey,JarOutputStream mOutputJar)throws IOException, GeneralSecurityException; - -} diff --git a/src/processing/mode/android/signing/JarSigner.java b/src/processing/mode/android/signing/JarSigner.java deleted file mode 100644 index 331a72ca1..000000000 --- a/src/processing/mode/android/signing/JarSigner.java +++ /dev/null @@ -1,36 +0,0 @@ -package processing.mode.android.signing; - -import java.io.*; -import java.security.*; -import java.security.cert.X509Certificate; - -/** - * Created by ibziy_000 on 17.08.2014. - */ -public class JarSigner { - public static void signJar(File jarToSign, File outputJar, String alias, String keypass, String keystore, String storepass) - throws GeneralSecurityException, IOException, SignedJarBuilder.IZipEntryFilter.ZipAbortException { - - PrivateKey mPrivateKey; - X509Certificate mCertificate; - - KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - FileInputStream fis = new FileInputStream(keystore); - keyStore.load(fis, storepass.toCharArray()); - fis.close(); - - KeyStore.PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry( - alias, new KeyStore.PasswordProtection(keypass.toCharArray())); - if (entry != null) { - mPrivateKey = entry.getPrivateKey(); - mCertificate = (X509Certificate) entry.getCertificate(); - } else { - throw new KeyStoreException("Couldn't get key"); - } - - SignedJarBuilder builder = new SignedJarBuilder( - new FileOutputStream(outputJar, false), mPrivateKey, mCertificate); - builder.writeZip(new FileInputStream(jarToSign), null); - builder.close(); - } -} diff --git a/src/processing/mode/android/signing/SignedJarBuilder.java b/src/processing/mode/android/signing/SignedJarBuilder.java deleted file mode 100644 index 268aad229..000000000 --- a/src/processing/mode/android/signing/SignedJarBuilder.java +++ /dev/null @@ -1,417 +0,0 @@ -package processing.mode.android.signing; - -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.security.DigestOutputStream; -import java.security.GeneralSecurityException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.jar.Attributes; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import sun.misc.BASE64Encoder; -import sun.security.pkcs.ContentInfo; -import sun.security.pkcs.PKCS7; -import sun.security.pkcs.SignerInfo; -import sun.security.x509.AlgorithmId; -import sun.security.x509.X500Name; - -/** - * A Jar file builder with signature support. - */ -public class SignedJarBuilder { - private static final String DIGEST_ALGORITHM = "SHA1"; - private static final String DIGEST_ATTR = "SHA1-Digest"; - private static final String DIGEST_MANIFEST_ATTR = "SHA1-Digest-Manifest"; - - /** Write to another stream and also feed it to the Signature object. */ - private static class SignatureOutputStream extends FilterOutputStream { - private Signature mSignature; - private int mCount = 0; - /** Some signature providers need to use the original message (CERT.SF) to provide - *a signature block. Caching it in the contents variable for future use.*/ - private List contents = new ArrayList(); - - public SignatureOutputStream(OutputStream out, Signature sig) { - super(out); - mSignature = sig; - } - - @Override - public void write(int b) throws IOException { - try { - mSignature.update((byte) b); - contents.add((byte)b); - } catch (SignatureException e) { - throw new IOException("SignatureException: " + e); - } - super.write(b); - mCount++; - } - - @Override - public void write(byte[] b, int off, int len) throws IOException { - try { - mSignature.update(b, off, len); - for(byte myByte:b) - contents.add(myByte); - } catch (SignatureException e) { - throw new IOException("SignatureException: " + e); - } - super.write(b, off, len); - mCount += len; - } - - public int size() { - return mCount; - } -// public byte[] getContents(){ -// byte[] result = new byte[contents.size()]; -// for(int i=0;itrue
    if the file should be included. - * @throws ZipAbortException if writing the file should be aborted. - */ - public boolean checkEntry(String archivePath) throws ZipAbortException; - } - - /** - * Creates a {@link SignedJarBuilder} with a given output stream, and signing information. - *

    If either key or certificate is null then - * the archive will not be signed. - * @param out the {@link OutputStream} where to write the Jar archive. - * @param key the {@link PrivateKey} used to sign the archive, or null. - * @param certificate the {@link X509Certificate} used to sign the archive, or - * null. - * @throws IOException - * @throws NoSuchAlgorithmException - */ - public SignedJarBuilder(OutputStream out, PrivateKey key, X509Certificate certificate) - throws IOException, NoSuchAlgorithmException { - mOutputJar = new JarOutputStream(out); - mOutputJar.setLevel(9); - mKey = key; - mCertificate = certificate; - - if (mKey != null && mCertificate != null) { - mManifest = new Manifest(); - Attributes main = mManifest.getMainAttributes(); - main.putValue("Manifest-Version", "1.0"); - main.putValue("Created-By", "1.0 (Android)"); - - mBase64Encoder = new BASE64Encoder(); - mMessageDigest = MessageDigest.getInstance(DIGEST_ALGORITHM); - } - } - - /** - * Writes a new {@link File} into the archive. - * @param inputFile the {@link File} to write. - * @param jarPath the filepath inside the archive. - * @throws IOException - */ - public void writeFile(File inputFile, String jarPath) throws IOException { - // Get an input stream on the file. - FileInputStream fis = new FileInputStream(inputFile); - try { - - // create the zip entry - JarEntry entry = new JarEntry(jarPath); - entry.setTime(inputFile.lastModified()); - - writeEntry(fis, entry); - } finally { - // close the file stream used to read the file - fis.close(); - } - } - - /** - * Copies the content of a Jar/Zip archive into the receiver archive. - *

    An optional {@link IZipEntryFilter} allows to selectively choose which files - * to copy over. - * @param input the {@link InputStream} for the Jar/Zip to copy. - * @param filter the filter or null - * @throws IOException - */ - public void writeZip(InputStream input, IZipEntryFilter filter) - throws IOException, IZipEntryFilter.ZipAbortException { - ZipInputStream zis = new ZipInputStream(input); - - try { - // loop on the entries of the intermediary package and put them in the final package. - ZipEntry entry; - while ((entry = zis.getNextEntry()) != null) { - String name = entry.getName(); - - // do not take directories or anything inside a potential META-INF folder. - if (entry.isDirectory() || name.startsWith("META-INF/")) { - continue; - } - - // if we have a filter, we check the entry against it - if (filter != null && filter.checkEntry(name) == false) { - continue; - } - - JarEntry newEntry; - - // Preserve the STORED method of the input entry. - if (entry.getMethod() == JarEntry.STORED) { - newEntry = new JarEntry(entry); - } else { - // Create a new entry so that the compressed len is recomputed. - newEntry = new JarEntry(name); - } - - writeEntry(zis, newEntry); - - zis.closeEntry(); - } - } finally { - zis.close(); - } - } - - /** - * Closes the Jar archive by creating the manifest, and signing the archive. - * @throws IOException - * @throws GeneralSecurityException - */ - public void close() throws IOException, GeneralSecurityException { - if (mManifest != null) { - // write the manifest to the jar file - mOutputJar.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME)); - mManifest.write(mOutputJar); - - // CERT.SF - Signature signature = Signature.getInstance("SHA1with" + mKey.getAlgorithm()); - signature.initSign(mKey); - mOutputJar.putNextEntry(new JarEntry("META-INF/CERT.SF")); - //Caching the SignatureOutputStream object for future use by the signature provider extensions. - certFileContents = new SignatureOutputStream(mOutputJar, signature); - writeSignatureFile(certFileContents); - - // CERT.* - mOutputJar.putNextEntry(new JarEntry("META-INF/CERT." + mKey.getAlgorithm())); - writeSignature(signature, mCertificate, mKey); - } - - mOutputJar.close(); - mOutputJar = null; - } - - /** - * Clean up of the builder for interrupted workflow. - * This does nothing if {@link #close()} was called successfully. - */ - public void cleanUp() { - if (mOutputJar != null) { - try { - mOutputJar.close(); - } catch (IOException e) { - // pass - } - } - } - - /** - * Adds an entry to the output jar, and write its content from the {@link InputStream} - * @param input The input stream from where to write the entry content. - * @param entry the entry to write in the jar. - * @throws IOException - */ - private void writeEntry(InputStream input, JarEntry entry) throws IOException { - // add the entry to the jar archive - mOutputJar.putNextEntry(entry); - - // read the content of the entry from the input stream, and write it into the archive. - int count; - while ((count = input.read(mBuffer)) != -1) { - mOutputJar.write(mBuffer, 0, count); - - // update the digest - if (mMessageDigest != null) { - mMessageDigest.update(mBuffer, 0, count); - } - } - - // close the entry for this file - mOutputJar.closeEntry(); - - if (mManifest != null) { - // update the manifest for this entry. - Attributes attr = mManifest.getAttributes(entry.getName()); - if (attr == null) { - attr = new Attributes(); - mManifest.getEntries().put(entry.getName(), attr); - } - attr.putValue(DIGEST_ATTR, mBase64Encoder.encode(mMessageDigest.digest())); - } - } - - /** Writes a .SF file with a digest to the manifest. */ - private void writeSignatureFile(SignatureOutputStream out) - throws IOException, GeneralSecurityException { - Manifest sf = new Manifest(); - Attributes main = sf.getMainAttributes(); - main.putValue("Signature-Version", "1.0"); - main.putValue("Created-By", "1.0 (Android)"); - - BASE64Encoder base64 = new BASE64Encoder(); - MessageDigest md = MessageDigest.getInstance(DIGEST_ALGORITHM); - PrintStream print = new PrintStream( - new DigestOutputStream(new ByteArrayOutputStream(), md), - true, "UTF-8"); - - // Digest of the entire manifest - mManifest.write(print); - print.flush(); - main.putValue(DIGEST_MANIFEST_ATTR, base64.encode(md.digest())); - - Map entries = mManifest.getEntries(); - for (Map.Entry entry : entries.entrySet()) { - // Digest of the manifest stanza for this entry. - print.print("Name: " + entry.getKey() + "\r\n"); - for (Map.Entry att : entry.getValue().entrySet()) { - print.print(att.getKey() + ": " + att.getValue() + "\r\n"); - } - print.print("\r\n"); - print.flush(); - - Attributes sfAttr = new Attributes(); - sfAttr.putValue(DIGEST_ATTR, base64.encode(md.digest())); - sf.getEntries().put(entry.getKey(), sfAttr); - } - - sf.write(out); - - // A bug in the java.util.jar implementation of Android platforms - // up to version 1.6 will cause a spurious IOException to be thrown - // if the length of the signature file is a multiple of 1024 bytes. - // As a workaround, add an extra CRLF in this case. - if ((out.size() % 1024) == 0) { - out.write('\r'); - out.write('\n'); - } - } - - /** Write the certificate file with a digital signature. */ - private void writeSignatureBlock(Signature signature, X509Certificate publicKey, - PrivateKey privateKey) - throws IOException, GeneralSecurityException { - SignerInfo signerInfo = new SignerInfo( - new X500Name(publicKey.getIssuerX500Principal().getName()), - publicKey.getSerialNumber(), - AlgorithmId.get(DIGEST_ALGORITHM), - AlgorithmId.get(privateKey.getAlgorithm()), - signature.sign()); - - PKCS7 pkcs7 = new PKCS7( - new AlgorithmId[] { AlgorithmId.get(DIGEST_ALGORITHM) }, - new ContentInfo(ContentInfo.DATA_OID, null), - new X509Certificate[] { publicKey }, - new SignerInfo[] { signerInfo }); - - pkcs7.encodeSignedData(mOutputJar); - } - - - /** - * Check for extension implementations and if found use the contributed class - * to write the signature block. Otherwise use the default implementation. - * - * @param signature - * @param publicKey - * @param privateKey - * @throws IOException - * @throws GeneralSecurityException - */ - private void writeSignature(Signature signature, X509Certificate publicKey, - PrivateKey privateKey) - throws IOException, GeneralSecurityException{ - writeSignatureBlock(signature, publicKey, privateKey); - } -} \ No newline at end of file diff --git a/theme/buttons-2x.png b/theme/buttons-2x.png deleted file mode 100644 index 6e951b8b8..000000000 Binary files a/theme/buttons-2x.png and /dev/null differ diff --git a/theme/buttons.png b/theme/buttons.png deleted file mode 100644 index 4058c51d9..000000000 Binary files a/theme/buttons.png and /dev/null differ diff --git a/theme/mode-2x.png b/theme/mode-2x.png deleted file mode 100644 index 25b98ebdb..000000000 Binary files a/theme/mode-2x.png and /dev/null differ diff --git a/todo.txt b/todo.txt deleted file mode 100644 index 0ea6b7e57..000000000 --- a/todo.txt +++ /dev/null @@ -1,365 +0,0 @@ -0218 android -X add support for icon-96 for xhdpi icons -X https://github.com/processing/processing-android/issues/37 -X switch to onBackPressed() for back button handling -X developer.android.com/reference/android/app/Activity.html#onBackPressed() -X https://github.com/processing/processing-android/pull/52 -X https://github.com/processing/processing-android/issues/50 -X sync PVector plus the processing.data package -X https://github.com/processing/processing-android/issues/47 -X https://github.com/processing/processing-android/pull/42 -X "Smooth is not supported by this hardware (or driver)" message -X even with Nexus 4, Android 4.2.2 -X https://github.com/processing/processing-android/issues/2 -X "Bitmap size exceeds VM budget" error when loading many images -X https://github.com/processing/processing-android/issues/60 -_ seems to not be printing newlines and indents when writing the xml files - -gsoc -X fixes for ecj compilation -X https://github.com/processing/processing-android/pull/58 -X implement certificates (self-signed) for distribution -X http://developer.android.com/guide/publishing/app-signing.html -X http://code.google.com/p/processing/issues/detail?id=222 -X https://github.com/processing/processing-android/issues/15 -X implement automatic download/install of android tools -X also need to install USB Driver on Windows, and set device rules on Linux -X http://code.google.com/p/processing/issues/detail?id=203 -X https://github.com/processing/processing-android/issues/20 - -_ make Android write .jar not .zip - -_ figure out how to build from Eclipse JDI so we can remove tools.jar and javac -_ https://github.com/processing/processing/issues/1840 -_ figure out Android build w/o javac so we can remove tools.jar and javac -_ also to the p5 repo with just a JRE -_ remove initRequirements from Base (no longer need JDI) -_ move this into Android mode? - -_ temporary files (for sketches and logs) are not deleted -o http://code.google.com/p/processing/issues/detail?id=562 -_ https://github.com/processing/processing-android/issues/33 - -_ NullPointerException in AndroidBuild.writeLocalProps(AndroidBuild.java:458) -_ prompts for SDK, works; then after restart breaks again -_ also refers to ANDROID_HOME and not ANDROID_SDK.. -_ are we using the right one these days? -_ http://code.google.com/p/processing/issues/detail?id=979 -_ this one is difficult to reproduce - -_ Android emulator doesn't always start on the first attempt -_ emulator not starting up on OS X? -_ http://code.google.com/p/processing/issues/detail?id=1210 - -_ Android OPENGL renderer + JAVA2D PGraphics results in PTexture exception -_ http://code.google.com/p/processing/issues/detail?id=1019 - -_ focus handling note: -_ http://android-developers.blogspot.com/2011/11/making-android-games-that-play-nice.html - -_ if a sketch asks for Android mode but it's not available -_ (after a double-click) -_ you get the "is android installed"? dialog, then it re-opens again -_ without closing the other - -_ don't let the examples get overwritten with mode settings, manifest, etc -_ the whole sketch.properties thing is yech - -_ add INTERNET permissions to the android net examples -_ or other necessary permissions for other examples - -_ go through all basics/topics examples -_ remove those that don't make sense with android -_ remove size() commands from most/all -_ (or remove ones that truly require size...) -_ optimize for android use -_ need to set permissions as necessary (therefore add manifest files) - -lifecycle/size changes/etc -_ need to smooth out screen orientation changes -_ g2 and g3 are no longer disposed on pause (0195), but probably should be -_ they're deleted when dispose() is called (on exit()) -_ add registered methods again -_ need to figure out generic event queueing first -_ may need a different subset of methods, and introduce new ones -_ that will be usable on both android and desktop -_ dispose() was calling disposeMethods.handle(), but they're null -_ possible major issue with sketches not quitting out of run() when in bg -_ pause needs to actually kill the thread -_ returning from pause needs to reset the clock -_ this is currently draining batteries -_ thread is continually running - 'inside handleDraw()' running continually -_ inside run() it shouldn't still be going -_ avoid sketch restart on orientation change -_ need sizeChanged() method... -_ also add the param to the xml file saying that it can deal w/ rotation -_ https://github.com/processing/processing/issues/1640 - -_ re: android libraries, from shawn van every -The most powerful part were the libraries (and the ease with which they could be developed). Location, SMS, Camera/Video, Bluetooth (for Arduino integration) and PClient/PRequest were by far the most used. The ones that came with it, plus the ones from MJSoft were good though I ended up making a couple of very specific ones for my students: http://www.mobvcasting.com/wp/?cat=4 - -_ process trackball events (they're only deltas) -_ implement link() - -_ error in 'create avd' with "Emulator already exists" when it needs an upgrade -_ or cannot be used with the current setup -_ use 'android list avds' on the command line to see the problem in this case -_ when there's a 'create avd' error, things still keep running. yay! - -_ need to do this for utf8: "overridable Ant javac properties: java.encoding" -_ new for sdk tools r8, it's using ascii as the default, we're utf-8 - -_ don't give user a "User cancelled attempt to find SDK" error message -_ it's annoying.. they f*king know they just did that -_ also gives an error if it unsets the sdk path itself, saying that the -_ environment variable isn't set. which isn't true--it's set, but it doesn't -_ think the location is valid, which is totally different. -_ ...because it's ignoring the exception messages that come in from trying -_ to create the new sdk object - -_ need to do something to make it easier to do new screen sizes. - -_ sketches must be removed manually if the debug keystore changes -_ http://code.google.com/p/processing/issues/detail?id=236 - -_ "failed to get signature key" problem -_ Caused by: /Users/aandnota/Documents/android-sdk-mac_x86/tools/ant/ant_rules_r3.xml:209: com.android.sdklib.build.ApkCreationException: Unable to get debug signature key - -_ saveStream() on processing-android-core.zip breaks behind firewall -_ downloads a 5kb html login page rather than the correct file - -_ salaryper crashed when connecting to ctr500 and was re-routed -_ instead of sending back the gzip file, sent the error page -_ unlike java, where a 404 would give us null data - -add to wiki -_ add to wiki: 1MB file size is max for data folder -_ Data exceeds UNCOMPRESS_DATA_MAX (11840328 vs 1048576) -_ File storage = android.os.Environment.getExternalStorageDirectory(); -_ File folder = new File(storage, "awesomeapp"); -_ also check the data folder on run/export -_ add to wiki: orientation(PORTRAIT) and orientation(LANDSCAPE) -_ add to keywords.txt -_ ctrl-F12 (ctrl-fn-f12 on mac) will rotate the emulator - -android menu -_ something to bring up the full console window -_ signing tool -_ selection of which avd (emulator), or plugged-in devices (if multiple) - -_ throw an error if a file in the 'data' dir ends with .gz - -_ on export (application) -_ increment manifest/android:versionCode each time 'export' is called -_ Remove the android:debuggable="true" attribute from -_ provide manifest/android:versionName ('pretty' version number) -_ setting the default package: manifest/package -_ application/android:label -_ used on home screen, manage applications, my downloads, etc -_ http://developer.android.com/guide/publishing/preparing.html - -_ StreamPump has been quieted, but maybe this needs to be a global log setting - -_ seems to have problems on 64-bit windows -_ removing local version of java helped someone fix it - -_ don't let the keystore message show up in red -_ Using keystore: /Users/fry/.android/debug.keystore - -_ for libraries that don't work with android, don't let them export -_ http://code.google.com/p/processing/issues/detail?id=248 -_ add line for export in libraries to say whether they're compatible -_ even just 'android=' will be ok -_ or 'mode=java,android,python' - -_ error messages in runner that are handled special (OOME) need different -_ handling for android vs others.. argh - -_ clean up changes from andres -_ what is resetLights() in PGraphics? -_ remove model() method from end of PApplet (make it shape(PShape)) -_ PShape examples are totally broken - -P1 this is embarrassing, need to fix ASAP -P2 need to fix before beta release -P3 would like to fix before final release -P4 not an immediate need, but very nice to have -P5 nice to have - -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - -CORE (PApplet, P2D et al) - -_ implement blendMode() for Android -_ should be fairly straightforward given Java2D implementation -_ http://code.google.com/p/processing/issues/detail?id=1386 -_ Finish implementation of OPEN and CHORD drawing modes for arc() -_ http://code.google.com/p/processing/issues/detail?id=1405 - -_ images resized with default renderer on Android are pixelated -_ http://code.google.com/p/processing/issues/detail?id=552 - -_ implement tap detection and set correct click count for mouseClicked() -_ mouseClicked is currently not fired at all (no direct match on Android) -_ http://code.google.com/p/processing/issues/detail?id=215 -_ keyTyped() does not exist on Android -_ http://code.google.com/p/processing/issues/detail?id=1489 -_ implement multiple pointers and multi-touch -_ http://code.google.com/p/processing/issues/detail?id=243 - -_ Examples > Topics > Effects > Lens uses a ton of memory - - -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - -TOOLS - -_ errors in ActivityManager aren't coming through -_ if AVD is deleted while processing still running, things flake out -_ also no error messages, just 'giving up on launching emulator' - -// jdf maybedone -_ when out of memory, need an error message to show up in the PDE -_ show "OutOfMemoryError: bitmap size exceeds VM budget" in status area -_ Examples > Topics > Drawing > Animator produces: -_ Uncaught handler: thread Animation Thread exiting due to uncaught exception -_ java.lang.OutOfMemoryError: bitmap size exceeds VM budget -_ at android.graphics.Bitmap.nativeCreate(Native Method) - -// jdf maybedone -_ stack overflow produced no error inside the PDE -_ probably same as memory error above - -// jdf maybedone -_ if hitting 'run' in p5, need to kill any sketch that's currently running - -_ need to make data folder copy more efficient than just copying everything -_ right now, first copies to src inside Build.java (which then copies to bin) - -// jdf maybedone -_ other exceptions coming through System.err -W/System.err( 242): java.lang.IllegalArgumentException: File /data/data/processing.android.test.savemanyimages/files/circles-0001.tif contains a path separator -W/System.err( 242): at android.app.ApplicationContext.makeFilename(ApplicationContext.java:1444) -W/System.err( 242): at android.app.ApplicationContext.openFileOutput(ApplicationContext.java:386) -W/System.err( 242): at android.content.ContextWrapper.openFileOutput(ContextWrapper.java:158) -W/System.err( 242): at processing.core.PApplet.createOutput(PApplet.java:3677) - -P1 -_ no ES2 in the emulator, and no error reported in the PDE -_ problem is probably that the error comes via E/AndroidRuntime -_ java.lang.RuntimeException: Unable to start activity ComponentInfo{processing.test.fisheye/processing.test.fisheye.FishEye}: java.lang.RuntimeException: P3D: OpenGL ES 2.0 is not supported by this device. -_ http://developer.android.com/tools/devices/emulator.html -_ http://code.google.com/p/processing/issues/detail?id=1059 - -P2 -_ move the Android tools into its own source package in SVN -_ started, but needs proper Tool or Mode packaging -_ http://code.google.com/p/processing/issues/detail?id=206 -_ implement method for selecting the AVD -_ http://code.google.com/p/processing/issues/detail?id=208 -_ implement means to use Intel version of the emulator -_ need to verify if this is much faster or not -_ http://developer.android.com/tools/devices/emulator.html -_ http://android-developers.blogspot.com/2012/04/faster-emulator-with-better-hardware.html - -P3 _ for now, only runs on the first device (findDevice()) found -P3 _ --> implement selector to choose the default device for debugging -P3 _ http://code.google.com/p/processing/issues/detail?id=207 -P3 _ if different machines, debug.keystore changes, requiring manual removal -P3 _ or find a way to do it automatically with processing -P3 _ adb -s HT91MLC00031 install -r sketchbook/Hue/android/bin/Hue-debug.apk -P3 _ pkg: /data/local/tmp/Hue-debug.apk -P3 _ Failure [INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES] -P3 _ why does this result return 0? -P3 _ can't keep it with the sketch, don't want to give away private key -P3 _ with different machines, users are required to remove signature -P3 _ add a method to remove an application if the debug key is different -P3 _ perhaps the first time an application is installed, remove it? -P3 _ http://code.google.com/p/processing/issues/detail?id=236 -P3 _ library support also needs android manifest changes -P3 _ http://code.google.com/p/processing/issues/detail?id=225 - -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - -EXAMPLES - -_ simple example of reading the compass (also note that won't work w/ sim) -_ and also the gps, i assume (can do fake data w/ sim) - - -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - -SAVED FOR LATER - -_ may need to add screen orientation as a built-in function -_ fairly common to use, and otherwise needs an obscure import - -_ possibility of doing a compile (not run) using straight javac? -_ this would be a faster way to check for errors -_ w/o needing to use the incredibly slow android tools - -_ maybe the back button shouldn't quit apps, the home button should? -_ back button use in apps is so infuriating... - -_ separate "PApplet" into separate View and Activity classes -_ http://code.google.com/p/processing/issues/detail?id=212 -_ re-implement to use Fragment API -_ and what about daydream or widgets or whatever? -_ http://code.google.com/p/processing/issues/detail?id=1335 -_ implement size() and createGraphics() for arbitrary renderers -_ http://code.google.com/p/processing/issues/detail?id=241 - - -. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . - - -OPTIMIZE / ENHANCEMENTS - -_ don't re-calculate stroke() or fill() when it's the same value -_ should path.reset() or path.rewind() be used for a path to be reused? - -_ errors that cause a crash when setting sketchPath -_ seems to be a filesystem that got too full -_ no real signs of what went wrong, but deleting the avd fixed it -_ if it reappears again, trap that condition, and tell the user the fix - -_ show/hide the virtual keyboard -InputMethodManager imm = - (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); -imm.showSoftInput(surfaceView, 0); - -_ list contents of data folder (assets folder) - try { - PApplet.println(assets.list("")); - } catch (IOException e) { - e.printStackTrace(); - } - -_ excessive memory use indicator -_ D/dalvikvm( 1205): GC freed 814 objects / 523352 bytes in 58ms -_ could help show when lots of memory are being used - -try { - File root = Environment.getExternalStorageDirectory(); - if (root.canWrite()){ - File gpxfile = new File(root, "gpxfile.gpx"); - FileWriter gpxwriter = new FileWriter(gpxfile); - BufferedWriter out = new BufferedWriter(gpxwriter); - out.write("Hello world"); - out.close(); - } -} catch (IOException e) { - Log.e(TAG, "Could not write file " + e.getMessage()); -} - -_ application local storage: context.getFilesDir().getPath() -"For those of you interested, the internal 8GB of storage on the phone -is mounted at /emmc (r/w mode, of course) and microSD cards still -shows up normally at /sdcard as expected." - -_ other useful tidbits (handlers etc) -_ http://developer.android.com/guide/appendix/faq/commontasks.html