From cdecc8ba40066cf12c70c6f1aa5e4582b8f39662 Mon Sep 17 00:00:00 2001 From: John Hoford Date: Fri, 28 Jul 2023 12:09:29 -0700 Subject: [PATCH] extension library draft --- .../extensionLibrary/Compose/README.md | 19 + .../extensionLibrary/Compose/build.gradle.kts | 130 +++ .../Compose/gradle.properties | 3 + .../ext/graph3d/FunctionSetup.kt | 160 ++++ .../constraintlayout/ext/graph3d/Graph.kt | 64 ++ .../constraintlayout/ext/graph3d/Matrix.kt | 302 +++++++ .../constraintlayout/ext/graph3d/Object3D.kt | 300 ++++++ .../ext/graph3d/Quaternion.kt | 152 ++++ .../constraintlayout/ext/graph3d/Scene3D.kt | 603 +++++++++++++ .../ext/graph3d/VectorUtil.kt | 137 +++ .../ext/graph3d/ViewMatrix.kt | 325 +++++++ .../ext/graph3d/objects/AxisBox.kt | 197 ++++ .../ext/graph3d/objects/Surface3D.kt | 139 +++ .../ext/variableplot/VpGraphCompose.kt | 124 +++ .../ext/variableplot/VpGraphCore.kt | 462 ++++++++++ .../extensionLibrary/Views/README.md | 1 + .../extensionLibrary/Views/build.gradle.kts | 130 +++ .../extensionLibrary/Views/gradle.properties | 3 + .../constraintlayout/ext/graph3d/Graph.java | 146 +++ .../constraintlayout/ext/graph3d/Matrix.java | 374 ++++++++ .../ext/graph3d/Object3D.java | 244 +++++ .../ext/graph3d/Quaternion.java | 158 ++++ .../constraintlayout/ext/graph3d/Scene3D.java | 602 ++++++++++++ .../ext/graph3d/VectorUtil.java | 127 +++ .../ext/graph3d/ViewMatrix.java | 380 ++++++++ .../ext/graph3d/objects/AxisBox.java | 179 ++++ .../ext/graph3d/objects/Surface3D.java | 154 ++++ .../ext/variableplot/UiDelegate.java | 29 + .../constraintlayout/ext/variableplot/Vp.java | 501 ++++++++++ .../ext/variableplot/VpGraph.java | 65 ++ .../ext/variableplot/VpGraphCore.java | 499 ++++++++++ .../constraintlayout/ext/widget/LogJson.java | 853 ++++++++++++++++++ 32 files changed, 7562 insertions(+) create mode 100644 constraintlayout/extensionLibrary/Compose/README.md create mode 100644 constraintlayout/extensionLibrary/Compose/build.gradle.kts create mode 100644 constraintlayout/extensionLibrary/Compose/gradle.properties create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/FunctionSetup.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Graph.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Matrix.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Object3D.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Quaternion.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Scene3D.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/VectorUtil.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/ViewMatrix.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/AxisBox.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/Surface3D.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCompose.kt create mode 100644 constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCore.kt create mode 100644 constraintlayout/extensionLibrary/Views/README.md create mode 100644 constraintlayout/extensionLibrary/Views/build.gradle.kts create mode 100644 constraintlayout/extensionLibrary/Views/gradle.properties create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Graph.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Matrix.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Object3D.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Quaternion.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Scene3D.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/VectorUtil.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/ViewMatrix.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/AxisBox.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/Surface3D.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/UiDelegate.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/Vp.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraph.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraphCore.java create mode 100644 constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/widget/LogJson.java diff --git a/constraintlayout/extensionLibrary/Compose/README.md b/constraintlayout/extensionLibrary/Compose/README.md new file mode 100644 index 00000000..535bd228 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/README.md @@ -0,0 +1,19 @@ +# ConstraintLayout Extension Library for Jetpack Compose + +[![Maven Central](https://img.shields.io/maven-central/v/com.google.accompanist/accompanist-pager)](https://search.maven.org/search?q=g:com.google.accompanist) + +> :warning: This library has been deprecated as official support is now available in Compose 1.4.0. Please see our [Migration Guide](https://google.github.io/accompanist/pager/) for how to migrate. + +For more information, visit the documentation: https://google.github.io/accompanist/pager + +## Download + +```groovy +repositories { + mavenCentral() +} + +dependencies { + implementation 'androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha07' +} +``` diff --git a/constraintlayout/extensionLibrary/Compose/build.gradle.kts b/constraintlayout/extensionLibrary/Compose/build.gradle.kts new file mode 100644 index 00000000..1f3ab7dd --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/build.gradle.kts @@ -0,0 +1,130 @@ +/* + * Copyright 2023 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 + * + * 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.pager" + + compileSdk = 34 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + disable += setOf("GradleOverrides") + } + + packaging { + // Some of the META-INF files conflict with coroutines-test. Exclude them to enable + // our test APK to build (has no effect on our AARs) + resources { + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + named("androidTest") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + api(libs.compose.foundation.foundation) + api(libs.snapper) + + implementation(libs.napier) + implementation(libs.kotlin.coroutines.android) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(project(":testharness")) + testImplementation(project(":testharness")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.truth) + testImplementation(libs.truth) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.compose.ui.test.manifest) + testImplementation(libs.compose.ui.test.manifest) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + testImplementation(libs.robolectric) +} diff --git a/constraintlayout/extensionLibrary/Compose/gradle.properties b/constraintlayout/extensionLibrary/Compose/gradle.properties new file mode 100644 index 00000000..03a51122 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=accompanist-pager +POM_NAME=Accompanist Pager layouts +POM_PACKAGING=aar \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/FunctionSetup.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/FunctionSetup.kt new file mode 100644 index 00000000..cf2a00d9 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/FunctionSetup.kt @@ -0,0 +1,160 @@ +package com.google.constraintlayout.ext.graph3d + +import android.support.composegraph3d.lib.objects.AxisBox +import android.support.composegraph3d.lib.objects.Surface3D +import android.support.composegraph3d.lib.objects.Surface3D.Function +import java.util.* +import kotlin.math.* + +class FunctionSetup(var mWidth: Int, var mHeight: Int) { + var mScene3D: Scene3D + private var mImageBuff: IntArray + var mGraphType = 2 + private var mLastTouchX0 = Float.NaN + private var mLastTouchY0 = 0f + private var mLastTrackBallX = 0f + private var mLastTrackBallY = 0f + var mDownScreenWidth = 0.0 + var mSurface: Surface3D? = null + var mAxisBox: AxisBox? = null + var range = 20f + var minZ = -10f + var maxZ = 10f + var mZoomFactor = 1f + var animated = false + var zBuff: FloatArray = FloatArray(mWidth * mHeight) + var nanoTime: Long = 0 + var time = 0f + + fun buildSurface() { + mSurface = Surface3D(mFunction = object : Function { + override fun eval(x: Float, y: Float): Float { + val d = Math.sqrt((x * x + y * y).toDouble()) + return 0.3f * (Math.cos(d) * (y * y - x * x) / (1 + d)).toFloat() + } + }) + mSurface!!.setRange(-range, range, -range, range, minZ, maxZ) + mScene3D.setObject(mSurface!!) + mScene3D.resetCamera() + mAxisBox = AxisBox() + mAxisBox!!.setRange(-range, range, -range, range, minZ, maxZ) + mScene3D.addPostObject(mAxisBox!!) + return buildAnimatedSurface() + } + + + init { + mImageBuff = IntArray(mWidth * mHeight) + // zBuff = new float[w*h]; + mScene3D = Scene3D() + buildSurface() + mScene3D.setUpMatrix(mWidth, mHeight) + mScene3D.setScreenDim(mWidth, mHeight, mImageBuff, 0x00AAAAAA) + } + + fun buildAnimatedSurface() { + mSurface = Surface3D(object : Function { + override fun eval(x: Float, y: Float): Float { + val d = sqrt((x * x + y * y).toDouble()).toFloat() + val d2 = (x * x + y * y).toDouble().pow(0.125).toFloat() + val angle = atan2(y.toDouble(), x.toDouble()).toFloat() + val s = sin((d + angle - time * 5).toDouble()).toFloat() + val s2 = sin(time.toDouble()).toFloat() + val c = cos((d + angle - time * 5).toDouble()).toFloat() + return (s2 * s2 + 0.1f) * d2 * 5 * (s + c) / (1 + d * d / 20) + } + }) + nanoTime = System.nanoTime() + mScene3D.setObject(mSurface!!) + mSurface!!.setRange(-range, range, -range, range, minZ, maxZ) + } + + fun tick(now: Long) { + time += (now - nanoTime) * 1E-9f + nanoTime = now + mSurface!!.calcSurface(false) + mScene3D.update() + } + + fun onKeyTyped(c: Long) { + println(c) + // switch ((char) c) { +// case ' ': +// buildAnimatedSurface(); +// } + } + + fun onMouseDown(x: Float, y: Float) { + mDownScreenWidth = mScene3D.screenWidth + mLastTouchX0 = x + mLastTouchY0 = y + mScene3D.trackBallDown(mLastTouchX0, mLastTouchY0) + mLastTrackBallX = mLastTouchX0 + mLastTrackBallY = mLastTouchY0 + } + + fun onMouseDrag(x: Float, y: Float) { + if (java.lang.Float.isNaN(mLastTouchX0)) { + return + } + val moveX = mLastTrackBallX - x + val moveY = mLastTrackBallY - y + if (moveX * moveX + moveY * moveY < 4000f) { + mScene3D.trackBallMove(x, y) + } + mLastTrackBallX = x + mLastTrackBallY = y + } + + fun onMouseUP() { + mLastTouchX0 = Float.NaN + mLastTouchY0 = Float.NaN + } + + fun onMouseWheel(rotation: Float, ctlDown: Boolean) { + if (ctlDown) { + mZoomFactor *= 1.01.pow(rotation.toDouble()).toFloat() + mScene3D.zoom = mZoomFactor + mScene3D.setUpMatrix(mWidth, mHeight) + mScene3D.update() + } else { + range *= 1.01.pow(rotation.toDouble()).toFloat() + mSurface!!.setArraySize(Math.min(300, (range * 5).toInt())) + mSurface!!.setRange(-range, range, -range, range, minZ, maxZ) + mAxisBox!!.setRange(-range, range, -range, range, minZ, maxZ) + mScene3D.update() + } + } + + fun getImageBuff(time: Long): IntArray { + tick(time) + if (mScene3D.notSetUp()) { + mScene3D.setUpMatrix(mWidth, mHeight) + } + render(2) + return mImageBuff + } + + fun render(type: Int) { + Arrays.fill(mImageBuff, -0x777778) + mScene3D.render(2) + + // Arrays.fill(mScene3D.getZBuff(),Float.MAX_VALUE); + + // mSurface.render(this, zBuff, mImageBuff, mWidth, mHeight); + // raster_phong(mSurface,mScene3D,zBuff,mImageBuff,mWidth,mHeight); + } + + fun setSize(width: Int, height: Int) { + if (mWidth == width && mHeight == height) { + return + } + println("$width $height") + mWidth = width + mHeight = height + mImageBuff = IntArray(mWidth * mHeight) + buildSurface() + mScene3D.setUpMatrix(mWidth, mHeight) + mScene3D.setScreenDim(mWidth, mHeight, mImageBuff, 0x00AAAAAA) + } /////////////////////////////////////// +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Graph.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Graph.kt new file mode 100644 index 00000000..8bfe27ff --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Graph.kt @@ -0,0 +1,64 @@ +package com.google.constraintlayout.ext.graph3d + +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.ImageBitmapConfig +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.compose.ui.input.pointer.PointerInputChange + +class Graph { + var w = 512 + var h = 512 + val scale = 2 + var downX = 0.0f + var downY = 0.0f + var last = System.nanoTime() + var count = 0; + val showFps = true; + var graphFunctions = FunctionSetup(w, h) + var bitmap = ImageBitmap(w, h, ImageBitmapConfig.Argb8888) + fun setSize(width: Int, height: Int) { + if (w == width/scale && h == height/scale) { + return + } + w = width/scale + h = height/scale + graphFunctions.setSize(w, h) + bitmap = ImageBitmap(w, h, ImageBitmapConfig.Argb8888) + println("$w x $h") + } + + fun getImageForTime(nanoTime: Long): ImageBitmap { + val pix = graphFunctions.getImageBuff(nanoTime) + bitmap.asAndroidBitmap().setPixels(pix, 0, w, 0, 0, w, h) + if (showFps) { + count++ + val now = System.nanoTime() + if ((now - last) > 1000000000) { + println("rate : " + count / ((now - last).toFloat() * 1E-9f) + "f/sec") + last = now; + count = 0; + } + } + return bitmap + } + + fun dragStart(down: Offset) { + downX = down.x/scale + downY = down.y/scale + graphFunctions.onMouseDown(downX, downY) + } + + fun dragStopped() { + downX = 0.0f + downY = 0.0f + } + + fun drag(change: PointerInputChange, drag: Offset) { + downX += drag.x/scale + downY += drag.y/scale + graphFunctions.onMouseDrag(downX, downY) + + } + +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Matrix.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Matrix.kt new file mode 100644 index 00000000..ecacbbb9 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Matrix.kt @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.graph3d + +import java.text.DecimalFormat +import java.util.* + +/** + * Matrix math class. (For the purposes of this application it is more efficient as has no JNI) + */ +open class Matrix { + var m: DoubleArray + fun makeRotation() { + run { + val v = doubleArrayOf(m[0], m[4], m[8]) + VectorUtil.normalize(v) + m[0] = v[0] + m[4] = v[1] + m[8] = v[2] + } + run { + val v = doubleArrayOf(m[1], m[5], m[9]) + VectorUtil.normalize(v) + m[1] = v[0] + m[5] = v[1] + m[9] = v[2] + } + run { + val v = doubleArrayOf(m[2], m[6], m[10]) + VectorUtil.normalize(v) + m[2] = v[0] + m[6] = v[1] + m[10] = v[2] + } + } + + open fun print() { + val df = DecimalFormat(" ##0.000") + for (i in 0..3) { + for (j in 0..3) { + print( + (if (j == 0) "[ " else " , ") + trim( + df.format( + m[i * 4 + j] + ) + ) + ) + } + println("]") + } + } + + constructor() { + m = DoubleArray(4 * 4) + setToUnit() + } + + constructor(matrix: Matrix) : this(Arrays.copyOf(matrix.m, matrix.m.size)) {} + protected constructor(m: DoubleArray) { + this.m = m + } + + fun setToUnit() { + for (i in 1 until m.size) { + m[i] = 0.0 + } + m[0] = 1.0 + m[5] = 1.0 + m[10] = 1.0 + m[15] = 1.0 + } + + fun mult4(src: FloatArray, dest: FloatArray) { + for (i in 0..3) { + val col = i * 4 + var sum = 0.0 + for (j in 0..3) { + sum += m[col + j] * src[j] + } + dest[i] = sum.toFloat() + } + } + + fun mult3(src: FloatArray, dest: FloatArray) { + for (i in 0..2) { + val col = i * 4 + var sum = m[col + 3] + for (j in 0..2) { + sum += m[col + j] * src[j] + } + dest[i] = sum.toFloat() + } + } + + fun mult3v(src: FloatArray, dest: FloatArray) { + for (i in 0..2) { + val col = i * 4 + var sum = 0.0 + for (j in 0..2) { + sum += m[col + j] * src[j] + } + dest[i] = sum.toFloat() + } + } + + fun mult3v(src: FloatArray, off: Int, dest: FloatArray) { + for (i in 0..2) { + val col = i * 4 + var sum = 0.0 + for (j in 0..2) { + sum += m[col + j] * src[off + j] + } + dest[i] = sum.toFloat() + } + } + + fun mult4(src: DoubleArray, dest: DoubleArray) { + for (i in 0..3) { + val col = i * 4 + var sum = 0.0 + for (j in 0..3) { + sum += m[col + j] * src[j] + } + dest[i] = sum.toFloat().toDouble() + } + } + + fun mult3(src: DoubleArray, dest: DoubleArray) { + for (i in 0..2) { + val col = i * 4 + var sum = m[col + 3] + for (j in 0..2) { + sum += m[col + j] * src[j] + } + dest[i] = sum.toFloat().toDouble() + } + } + + fun mult3v(src: DoubleArray?, dest: DoubleArray) { + for (i in 0..2) { + val col = i * 4 + var sum = 0.0 + for (j in 0..2) { + sum += m[col + j] * src!![j] + } + dest[i] = sum.toFloat().toDouble() + } + } + + fun vecmult(src: DoubleArray?): DoubleArray { + val ret = DoubleArray(3) + mult3v(src, ret) + return ret + } + + fun mult3(src: FloatArray, off1: Int, dest: FloatArray, off2: Int) { + var col = 0 * 4 + var sum = m[col + 3] + for (j in 0..2) { + sum += m[col + j] * src[j + off1] + } + val v0 = sum.toFloat() + col = 1 * 4 + sum = m[col + 3] + for (j in 0..2) { + sum += m[col + j] * src[j + off1] + } + val v1 = sum.toFloat() + col = 2 * 4 + sum = m[col + 3] + for (j in 0..2) { + sum += m[col + j] * src[j + off1] + } + val v2 = sum.toFloat() + dest[off2] = v0 + dest[1 + off2] = v1 + dest[2 + off2] = v2 + } + + fun invers(ret: Matrix): Matrix? { + val inv = ret.m + inv[0] = + m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10] + inv[4] = + -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + + m[12] * m[7] * m[10] + inv[8] = + m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9] + inv[12] = + -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + + m[12] * m[6] * m[9] + inv[1] = + -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + + m[13] * m[3] * m[10] + inv[5] = + m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10] + inv[9] = + -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + + m[12] * m[3] * m[9] + inv[13] = + m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9] + inv[2] = + m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6] + inv[6] = + -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + + m[12] * m[3] * m[6] + inv[10] = + m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5] + inv[14] = + -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + + m[12] * m[2] * m[5] + inv[3] = + -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + + m[9] * m[3] * m[6] + inv[7] = + m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6] + inv[11] = + -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + + m[8] * m[3] * m[5] + inv[15] = + m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5] + var det: Double + det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12] + if (det == 0.0) { + return null + } + det = 1.0 / det + for (i in 0..15) { + inv[i] = inv[i] * det + } + return ret + } + + fun mult(b: Matrix?): Matrix { + return Matrix( + multiply( + m, b!!.m + ) + ) + } + + fun premult(b: Matrix): Matrix { + return Matrix(multiply(b.m, m)) + } + + companion object { + private fun trim(s: String): String { + return s.substring(s.length - 7) + } + + private fun multiply(a: DoubleArray, b: DoubleArray): DoubleArray { + val resultant = DoubleArray(16) + for (i in 0..3) { + for (j in 0..3) { + for (k in 0..3) { + resultant[i + 4 * j] += a[i + 4 * k] * b[k + 4 * j] + } + } + } + return resultant + } + + @JvmStatic + fun main(args: Array) { + val m = Matrix() + val inv = Matrix() + m.m[0] = 100.0 + m.m[5] = 12.0 + m.m[10] = 63.0 + m.m[3] = 12.0 + m.m[7] = 34.0 + m.m[11] = 17.0 + println(" matrix ") + m.print() + println(" inv ") + m.invers(inv)!!.print() + println(" inv*matrix ") + m.mult(m.invers(inv)).print() + } + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Object3D.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Object3D.kt new file mode 100644 index 00000000..57718309 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Object3D.kt @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.graph3d + +import android.support.composegraph3d.lib.Scene3D.Companion.trianglePhong +import android.support.composegraph3d.lib.objects.Surface3D + +/** + * This represents 3d Object in this system. + */ +open class Object3D { + lateinit var vert: FloatArray + lateinit var normal: FloatArray + lateinit var index: IntArray + lateinit var tVert : FloatArray + protected var mMinX = 0f + protected var mMaxX = 0f + protected var mMinY = 0f + protected var mMaxY = 0f + var mMinZ = 0f + var mMaxZ // bounds in x,y & z + = 0f + var type = 4 + var mAmbient = 0.3f + var mDefuse = 0.7f + var mSaturation = 0.6f + fun makeVert(n: Int) { + vert = FloatArray(n * 3) + tVert = FloatArray(n * 3) + normal = FloatArray(n * 3) + } + + fun makeIndexes(n: Int) { + index = IntArray(n * 3) + } + + fun transform(m: Matrix?) { + var i = 0 + while (i < vert.size) { + m!!.mult3(vert, i, tVert, i) + i += 3 + } + } + + open fun render(s: Scene3D, zbuff: FloatArray, img: IntArray, width: Int, height: Int) { + when (type) { + 0 -> raster_height(s, zbuff, img, width, height) + 1 -> raster_outline(s, zbuff, img, width, height) + 2 -> raster_color(s, zbuff, img, width, height) + 3 -> raster_lines(s, zbuff, img, width, height) + 4 -> rasterPhong(this,s, zbuff, img, width, height) + } + } + + private fun raster_lines(s: Scene3D, zbuff: FloatArray, img: IntArray, w: Int, h: Int) { + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + val height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3 + val `val` = (255 * Math.abs(height)).toInt() + Scene3D.Companion.triangle( + zbuff, img, 0x10001 * `val` + 0x100 * (255 - `val`), w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2] + ) + Scene3D.Companion.drawline( + zbuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f + ) + Scene3D.Companion.drawline( + zbuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f + ) + i += 3 + } + } + + fun raster_height(s: Scene3D?, zbuff: FloatArray, img: IntArray, w: Int, h: Int) { + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + var height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3 + height = (height - mMinZ) / (mMaxZ - mMinZ) + val col: Int = Scene3D.Companion.hsvToRgb( + height, + Math.abs(2 * (height - 0.5f)), + Math.sqrt(height.toDouble()).toFloat() + ) + Scene3D.Companion.triangle( + zbuff, img, col, w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2] + ) + i += 3 + } + } + + // float mSpec = 0.2f; + open fun raster_color(s: Scene3D, zbuff: FloatArray, img: IntArray, w: Int, h: Int) { + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + VectorUtil.triangleNormal(tVert, p1, p2, p3, s.tmpVec) + val defuse = VectorUtil.dot(s.tmpVec, s.mTransformedLight) + var height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3 + height = (height - mMinZ) / (mMaxZ - mMinZ) + val bright = Math.min(1f, Math.max(0f, mDefuse * defuse + mAmbient)) + val hue = (height - Math.floor(height.toDouble())).toFloat() + val sat = 0.8f + val col: Int = Scene3D.Companion.hsvToRgb(hue, sat, bright) + Scene3D.Companion.triangle( + zbuff, img, col, w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2] + ) + i += 3 + } + } + + private fun color(hue: Float, sat: Float, bright: Float): Int { + var hue = hue + var bright = bright + hue = hue(hue) + bright = bright(bright) + return Scene3D.Companion.hsvToRgb(hue, sat, bright) + } + + private fun hue(hue: Float): Float { + return (hue - Math.floor(hue.toDouble())).toFloat() + } + + private fun bright(bright: Float): Float { + return Math.min(1f, Math.max(0f, bright)) + } + + private fun defuse(normals: FloatArray, off: Int, light: FloatArray?): Float { + // s.mMatrix.mult3v(normal,off,s.tmpVec); + return Math.abs(VectorUtil.dot(normal, off, light)) + } + + fun raster_phong(s: Scene3D, zbuff: FloatArray, img: IntArray, w: Int, h: Int) { + var i = 0 + println(" render ") + + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + // VectorUtil.triangleNormal(tVert, p1, p2, p3, s.tmpVec); + + +// float defuse1 = VectorUtil.dot(normal, p1, s.mTransformedLight); +// float defuse2 = VectorUtil.dot(normal, p2, s.mTransformedLight); +// float defuse3 = VectorUtil.dot(normal, p3, s.mTransformedLight); + val defuse1 = defuse(normal, p1, s.mTransformedLight) + val defuse2 = defuse(normal, p2, s.mTransformedLight) + val defuse3 = defuse(normal, p3, s.mTransformedLight) + val col1_hue = hue((vert[p1 + 2] - mMinZ) / (mMaxZ - mMinZ)) + val col2_hue = hue((vert[p2 + 2] - mMinZ) / (mMaxZ - mMinZ)) + val col3_hue = hue((vert[p3 + 2] - mMinZ) / (mMaxZ - mMinZ)) + val col1_bright = bright(mDefuse * defuse1 + mAmbient) + val col2_bright = bright(mDefuse * defuse2 + mAmbient) + val col3_bright = bright(mDefuse * defuse3 + mAmbient) + Scene3D.Companion.trianglePhong( + zbuff, img, + col1_hue, col1_bright, + col2_hue, col2_bright, + col3_hue, col3_bright, + mSaturation, + w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] + ) + i += 3 + } + } + fun rasterPhong(mSurface: Object3D, s: Scene3D, zbuff: FloatArray?, img: IntArray?, w: Int, h: Int) { + var i = 0 + while (i < mSurface.index.size) { + val p1: Int = mSurface.index.get(i) + val p2: Int = mSurface.index.get(i + 1) + val p3: Int = mSurface.index.get(i + 2) + + // VectorUtil.triangleNormal(tVert, p1, p2, p3, s.tmpVec); + + +// float defuse1 = VectorUtil.dot(normal, p1, s.mTransformedLight); +// float defuse2 = VectorUtil.dot(normal, p2, s.mTransformedLight); +// float defuse3 = VectorUtil.dot(normal, p3, s.mTransformedLight); + val defuse1 = defuse(mSurface.normal, p1, s.mTransformedLight) + val defuse2 = defuse(mSurface.normal, p2, s.mTransformedLight) + val defuse3 = defuse(mSurface.normal, p3, s.mTransformedLight) + val col1_hue = + hue((mSurface.vert.get(p1 + 2) - mSurface.mMinZ) / (mSurface.mMaxZ - mSurface.mMinZ)) + val col2_hue = + hue((mSurface.vert.get(p2 + 2) - mSurface.mMinZ) / (mSurface.mMaxZ - mSurface.mMinZ)) + val col3_hue = + hue((mSurface.vert.get(p3 + 2) - mSurface.mMinZ) / (mSurface.mMaxZ - mSurface.mMinZ)) + val col1_bright = bright(mDefuse * defuse1 + mAmbient) + val col2_bright = bright(mDefuse * defuse2 + mAmbient) + val col3_bright = bright(mDefuse * defuse3 + mAmbient) + trianglePhong( + zbuff!!, img!!, + col1_hue, col1_bright, + col2_hue, col2_bright, + col3_hue, col3_bright, + 0.6f, + w, h, + mSurface.tVert.get(p1), mSurface.tVert.get(p1 + 1), mSurface.tVert.get(p1 + 2), + mSurface.tVert.get(p2), mSurface.tVert.get(p2 + 1), mSurface.tVert.get(p2 + 2), + mSurface.tVert.get(p3), mSurface.tVert.get(p3 + 1), mSurface.tVert.get(p3 + 2) + ) + i += 3 + } + } + + fun raster_outline(s: Scene3D, zBuff: FloatArray, img: IntArray, w: Int, h: Int) { + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + Scene3D.Companion.triangle( + zBuff, img, s.background, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] + ) + Scene3D.Companion.drawline( + zBuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] + ) + Scene3D.Companion.drawline( + zBuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] + ) + i += 3 + } + } + + fun center(): DoubleArray { + return doubleArrayOf( + ( + (mMinX + mMaxX) / 2).toDouble(), + ((mMinY + mMaxY) / 2).toDouble(), + ((mMinZ + mMaxZ) / 2 + ).toDouble() + ) + } + + fun centerX(): Float { + return (mMaxX + mMinX) / 2 + } + + fun centerY(): Float { + return (mMaxY + mMinY) / 2 + } + + fun rangeX(): Float { + return (mMaxX - mMinX) / 2 + } + + fun rangeY(): Float { + return (mMaxY - mMinY) / 2 + } + + fun size(): Double { + return Math.hypot( + (mMaxX - mMinX).toDouble(), + Math.hypot((mMaxY - mMinY).toDouble(), (mMaxZ - mMinZ).toDouble()) + ) / 2 + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Quaternion.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Quaternion.kt new file mode 100644 index 00000000..c8a35a54 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Quaternion.kt @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2020 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 com.google.constraintlayout.ext.graph3d + +/** + * This is a class that represents a Quaternion + * Used to implement a virtual trackball with no "gimbal lock" + * see https://en.wikipedia.org/wiki/Quaternion + */ +class Quaternion(x0: Double, x1: Double, x2: Double, x3: Double) { + private val x = DoubleArray(4) // w,x,y,z, + operator fun set(w: Double, x: Double, y: Double, z: Double) { + this.x[0] = w + this.x[1] = x + this.x[2] = y + this.x[3] = z + } + + operator fun set(v1: DoubleArray, v2: DoubleArray) { + val vec1 = normal(v1) + val vec2 = normal(v2) + val axis = normal(cross(vec1, vec2)) + val angle = Math.acos(dot(vec1, vec2)) + set(angle, axis) + } + + operator fun set(angle: Double, axis: DoubleArray?) { + x[0] = Math.cos(angle / 2) + val sin = Math.sin(angle / 2) + x[1] = axis!![0] * sin + x[2] = axis[1] * sin + x[3] = axis[2] * sin + } + + init { + x[0] = x0 + x[1] = x1 + x[2] = x2 + x[3] = x3 + } + + fun conjugate(): Quaternion { + return Quaternion(x[0], -x[1], -x[2], -x[3]) + } + + operator fun plus(b: Quaternion): Quaternion { + val a = this + return Quaternion(a.x[0] + b.x[0], a.x[1] + b.x[1], a.x[2] + b.x[2], a.x[3] + b.x[3]) + } + + operator fun times(b: Quaternion): Quaternion { + val a = this + val y0 = a.x[0] * b.x[0] - a.x[1] * b.x[1] - a.x[2] * b.x[2] - a.x[3] * b.x[3] + val y1 = a.x[0] * b.x[1] + a.x[1] * b.x[0] + a.x[2] * b.x[3] - a.x[3] * b.x[2] + val y2 = a.x[0] * b.x[2] - a.x[1] * b.x[3] + a.x[2] * b.x[0] + a.x[3] * b.x[1] + val y3 = a.x[0] * b.x[3] + a.x[1] * b.x[2] - a.x[2] * b.x[1] + a.x[3] * b.x[0] + return Quaternion(y0, y1, y2, y3) + } + + fun inverse(): Quaternion { + val d = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3] + return Quaternion(x[0] / d, -x[1] / d, -x[2] / d, -x[3] / d) + } + + fun divides(b: Quaternion): Quaternion { + val a = this + return a.inverse().times(b) + } + + fun rotateVec(v: DoubleArray?): DoubleArray { + val v0 = v!![0] + val v1 = v[1] + val v2 = v[2] + val s = x[1] * v0 + x[2] * v1 + x[3] * v2 + val n0 = 2 * (x[0] * (v0 * x[0] - (x[2] * v2 - x[3] * v1)) + s * x[1]) - v0 + val n1 = 2 * (x[0] * (v1 * x[0] - (x[3] * v0 - x[1] * v2)) + s * x[2]) - v1 + val n2 = 2 * (x[0] * (v2 * x[0] - (x[1] * v1 - x[2] * v0)) + s * x[3]) - v2 + return doubleArrayOf(n0, n1, n2) + } + + fun matrix() { + val xx = x[1] * x[1] + val xy = x[1] * x[2] + val xz = x[1] * x[3] + val xw = x[1] * x[0] + val yy = x[2] * x[2] + val yz = x[2] * x[3] + val yw = x[2] * x[0] + val zz = x[3] * x[3] + val zw = x[3] * x[0] + val m = DoubleArray(16) + m[0] = 1 - 2 * (yy + zz) + m[1] = 2 * (xy - zw) + m[2] = 2 * (xz + yw) + m[4] = 2 * (xy + zw) + m[5] = 1 - 2 * (xx + zz) + m[6] = 2 * (yz - xw) + m[8] = 2 * (xz - yw) + m[9] = 2 * (yz + xw) + m[10] = 1 - 2 * (xx + yy) + m[14] = 0.0 + m[13] = m[14] + m[12] = m[13] + m[11] = m[12] + m[7] = m[11] + m[3] = m[7] + m[15] = 1.0 + } + + companion object { + private fun cross(a: DoubleArray, b: DoubleArray): DoubleArray { + val out0 = a[1] * b[2] - b[1] * a[2] + val out1 = a[2] * b[0] - b[2] * a[0] + val out2 = a[0] * b[1] - b[0] * a[1] + return doubleArrayOf(out0, out1, out2) + } + + private fun dot(a: DoubleArray, b: DoubleArray): Double { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + } + + private fun normal(a: DoubleArray): DoubleArray { + val norm = Math.sqrt(dot(a, a)) + return doubleArrayOf(a[0] / norm, a[1] / norm, a[2] / norm) + } + + fun calcAngle(v1: DoubleArray, v2: DoubleArray): Double { + val vec1 = normal(v1) + val vec2 = normal(v2) + return Math.acos(dot(vec1, vec2)) + } + + fun calcAxis(v1: DoubleArray, v2: DoubleArray): DoubleArray { + val vec1 = normal(v1) + val vec2 = normal(v2) + return normal(cross(vec1, vec2)) + } + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Scene3D.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Scene3D.kt new file mode 100644 index 00000000..8debfec7 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/Scene3D.kt @@ -0,0 +1,603 @@ +/* + * Copyright (C) 2020 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 com.google.constraintlayout.ext.graph3d + +import java.util.* +import kotlin.math.abs +import kotlin.math.sqrt + +/** + * This renders 3Dimensional Objects. + */ +class Scene3D { + var mMatrix = ViewMatrix() + var mInverse: Matrix? = Matrix() + public var mObject3D: Object3D? = null + var mPreObjects: ArrayList = ArrayList() + var mPostObjects: ArrayList = ArrayList() + var zBuff: FloatArray? = null + lateinit var img: IntArray + private val light = floatArrayOf(0f, 0f, 1f) // The direction of the light source + + @JvmField + var mTransformedLight = floatArrayOf(0f, 1f, 1f) // The direction of the light source + var mLightMovesWithCamera = false + var width = 0 + var height = 0 + + @JvmField + var tmpVec = FloatArray(3) + var lineColor = -0x1000000 + private val epslonX = 0.000005232f + private val epslonY = 0.00000898f + private val mFunction: Function? = null + var zoom = 1f + var background = 0 + + internal inner class Box { + var m_box = arrayOf(floatArrayOf(1f, 1f, 1f), floatArrayOf(2f, 3f, 2f)) + var m_x1 = intArrayOf(0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0) + var m_y1 = intArrayOf(0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1) + var m_z1 = intArrayOf(0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1) + var m_x2 = intArrayOf(0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1) + var m_y2 = intArrayOf(0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1) + var m_z2 = intArrayOf(1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1) + var m_point1 = FloatArray(3) + var m_point2 = FloatArray(3) + var m_draw1 = FloatArray(3) + var m_draw2 = FloatArray(3) + fun drawLines(r: LineRender) { + for (i in 0..11) { + m_point1[0] = m_box[m_x1[i]][0] + m_point1[1] = m_box[m_y1[i]][1] + m_point1[2] = m_box[m_z1[i]][2] + m_point2[0] = m_box[m_x2[i]][0] + m_point2[1] = m_box[m_y2[i]][1] + m_point2[2] = m_box[m_z2[i]][2] + mInverse!!.mult3(m_point1, m_draw1) + mInverse!!.mult3(m_point2, m_draw2) + r.draw( + m_draw1[0].toInt(), + m_draw1[1].toInt(), + m_draw2[0].toInt(), + m_draw2[1].toInt() + ) + } + } + } + + init { + VectorUtil.normalize(light) + } + + fun transformTriangles() { + transform() + } + + fun transform() { + val m = mInverse + if (mLightMovesWithCamera) { + mMatrix.mult3v(light, mTransformedLight) + VectorUtil.normalize(mTransformedLight) + } else { + System.arraycopy(light, 0, mTransformedLight, 0, 3) + } + mObject3D!!.transform(m) + for (obj in mPreObjects) { + obj!!.transform(m) + } + for (obj in mPostObjects) { + obj!!.transform(m) + } + } + + var screenWidth: Double + get() = mMatrix.screenWidth + set(sw) { + mMatrix.screenWidth = sw + mMatrix.calcMatrix() + mMatrix.invers(mInverse!!) + transform() + } + + fun trackBallDown(x: Float, y: Float) { + mMatrix.trackBallDown(x, y) + mMatrix.invers(mInverse!!) + } + + fun trackBallMove(x: Float, y: Float) { + mMatrix.trackBallMove(x, y) + mMatrix.invers(mInverse!!) + transform() + } + + fun trackBallUP(x: Float, y: Float) { + mMatrix.trackBallUP(x, y) + mMatrix.invers(mInverse!!) + } + + fun update() { + mMatrix.invers(mInverse!!) + transform() + } + + fun panDown(x: Float, y: Float) { + mMatrix.panDown(x, y) + mMatrix.invers(mInverse!!) + } + + fun panMove(x: Float, y: Float) { + mMatrix.panMove(x, y) + mMatrix.invers(mInverse!!) + transform() + } + + fun panUP() { + mMatrix.panUP() + } + + val lookPoint: String + get() = Arrays.toString(mMatrix.lookPoint) + + fun setScreenDim(width: Int, height: Int, img: IntArray, background: Int) { + mMatrix.setScreenDim(width, height) + setupBuffers(width, height, img, background) + setUpMatrix(width, height) + transform() + } + + fun setUpMatrix(width: Int, height: Int) { + setUpMatrix(width, height, false) + } + + fun setUpMatrix(width: Int, height: Int, resetOrientation: Boolean) { + val look_point = mObject3D!!.center() + val diagonal = mObject3D!!.size() * zoom + mMatrix.lookPoint = look_point + if (resetOrientation) { + val eye_point = doubleArrayOf( + look_point!![0] - diagonal, + look_point[1] - diagonal, + look_point[2] + diagonal + ) + mMatrix.eyePoint = eye_point + val up_vector = doubleArrayOf(0.0, 0.0, 1.0) + mMatrix.upVector = up_vector + } else { + mMatrix.fixUpPoint() + } + val screenWidth = diagonal * 2 + mMatrix.screenWidth = screenWidth + mMatrix.setScreenDim(width, height) + mMatrix.calcMatrix() + mMatrix.invers(mInverse!!) + } + + fun notSetUp(): Boolean { + return mInverse == null + } + + interface LineRender { + fun draw(x1: Int, y1: Int, x2: Int, y2: Int) + } + + fun drawBox(g: LineRender?) {} + interface Function { + fun eval(x: Float, y: Float): Float + } + + fun addPreObject(obj: Object3D) { + mPreObjects.add(obj) + } + + fun setObject(obj: Object3D) { + mObject3D = obj + } + + fun addPostObject(obj: Object3D) { + mPostObjects.add(obj) + } + + fun resetCamera() { + setUpMatrix(width, height, true) + transform() + } + + fun setupBuffers(w: Int, h: Int, img: IntArray, background: Int) { + width = w + height = h + this.background = background + zBuff = FloatArray(w * h) + this.img = img + Arrays.fill(zBuff, Float.MAX_VALUE) + Arrays.fill(img, background) + } + + fun render(type: Int) { + if (zBuff == null) { + return + } + Arrays.fill(zBuff, Float.MAX_VALUE) + Arrays.fill(img, background) + for (mPreObject in mPreObjects) { + mPreObject!!.render(this, zBuff!!, img, width, height) + } + + mObject3D!!.render(this, zBuff!!, img, width, height) + for (mPreObject in mPostObjects) { + mPreObject!!.render(this, zBuff!!, img, width, height) + } + } + + companion object { + private const val TAG = "SurfaceGen" + private fun min(x1: Int, x2: Int, x3: Int): Int { + return if (x1 > x2) (if (x2 > x3) x3 else x2) else if (x1 > x3) x3 else x1 + } + + private fun max(x1: Int, x2: Int, x3: Int): Int { + return if (x1 < x2) (if (x2 < x3) x3 else x2) else if (x1 < x3) x3 else x1 + } + + fun hsvToRgb_slow(hue: Float, saturation: Float, value: Float): Int { + val h = (hue * 6).toInt() + val f = hue * 6 - h + val p = (0.5f + 255 * value * (1 - saturation)).toInt() + val q = (0.5f + 255 * value * (1 - f * saturation)).toInt() + val t = (0.5f + 255 * value * (1 - (1 - f) * saturation)).toInt() + val v = (0.5f + 255 * value).toInt() + when (h) { + 0 -> return -0x1000000 or (v shl 16) + (t shl 8) + p + 1 -> return -0x1000000 or (q shl 16) + (v shl 8) + p + 2 -> return -0x1000000 or (p shl 16) + (v shl 8) + t + 3 -> return -0x1000000 or (p shl 16) + (q shl 8) + v + 4 -> return -0x1000000 or (t shl 16) + (p shl 8) + v + 5 -> return -0x1000000 or (v shl 16) + (p shl 8) + q + } + return 0 + } + + fun hsvToRgb(hue: Float, saturation: Float, value: Float): Int { + val h = (hue * 6).toInt() + val f = hue * 6 - h + val p = (0.5f + 255 * value * (1 - saturation)).toInt() + val q = (0.5f + 255 * value * (1 - f * saturation)).toInt() + val t = (0.5f + 255 * value * (1 - (1 - f) * saturation)).toInt() + val v = (0.5f + 255 * value).toInt() + if (h == 0) { + return -0x1000000 or (v shl 16) + (t shl 8) + p + } + if (h == 1) { + return -0x1000000 or (q shl 16) + (v shl 8) + p + } + if (h == 2) { + return -0x1000000 or (p shl 16) + (v shl 8) + t + } + if (h == 3) { + return -0x1000000 or (p shl 16) + (q shl 8) + v + } + if (h == 4) { + return -0x1000000 or (t shl 16) + (p shl 8) + v + } + if (h == 5) { + return -0x1000000 or (v shl 16) + (p shl 8) + q + } + + return 0 + } + + + + @JvmStatic + fun drawline( + zbuff: FloatArray, img: IntArray, color: Int, w: Int, h: Int, + fx1: Float, fy1: Float, fz1: Float, + fx2: Float, fy2: Float, fz2: Float + ) { + val dx = fx2 - fx1 + val dy = fy2 - fy1 + val dz = fz2 - fz1 + val zang = sqrt(dy * dy + dz * dz) + val steps = sqrt(dx * dx + zang * zang) + var t = 0f + while (t < 1) { + val px = fx1 + t * dx + val py = fy1 + t * dy + val pz = fz1 + t * dz + val ipx = px.toInt() + val ipy = py.toInt() + if (ipx < 0 || ipx >= w || ipy < 0 || ipy >= h) { + t += 1 / steps + continue + } + val point = ipx + w * ipy + if (zbuff[point] >= pz - 2) { + img[point] = color + } + t += 1 / steps + } + } + + @JvmStatic + fun isBackface( + fx3: Float, fy3: Float, fz3: Float, + fx2: Float, fy2: Float, fz2: Float, + fx1: Float, fy1: Float, fz1: Float + ): Boolean { + return (fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2) < 0 + } + + @JvmStatic + fun triangle( + zbuff: FloatArray, img: IntArray, color: Int, w: Int, h: Int, + fx3: Float, fy3: Float, fz3: Float, + fx2: Float, fy2: Float, fz2: Float, + fx1: Float, fy1: Float, fz1: Float + ) { + var fx2 = fx2 + var fy2 = fy2 + var fz2 = fz2 + var fx1 = fx1 + var fy1 = fy1 + var fz1 = fz1 + if ((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2) < 0) { + val tmpx = fx1 + val tmpy = fy1 + val tmpz = fz1 + fx1 = fx2 + fy1 = fy2 + fz1 = fz2 + fx2 = tmpx + fy2 = tmpy + fz2 = tmpz + } + // using maxmima + // string(solve([x1*dx+y1*dy+zoff=z1,x2*dx+y2*dy+zoff=z2,x3*dx+y3*dy+zoff=z3],[dx,dy,zoff])); + val d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + ((fx2 - fx3) + * fy1)).toDouble() + if (d == 0.0) { + return + } + val dx = (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + ((fy2 - fy3) + * fz1)) / d).toFloat() + val dy = ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + ((fx2 - fx3) + * fz1)) / d).toFloat() + val zoff = (fx1 * (fy3 * fz2 - fy2 * fz3) + (fy1 + * (fx2 * fz3 - fx3 * fz2)) + (fx3 * fy2 - fx2 * fy3) * fz1 / d).toFloat() + + // 28.4 fixed-point coordinates + val Y1 = (16.0f * fy1 + .5f).toInt() + val Y2 = (16.0f * fy2 + .5f).toInt() + val Y3 = (16.0f * fy3 + .5f).toInt() + val X1 = (16.0f * fx1 + .5f).toInt() + val X2 = (16.0f * fx2 + .5f).toInt() + val X3 = (16.0f * fx3 + .5f).toInt() + val DX12 = X1 - X2 + val DX23 = X2 - X3 + val DX31 = X3 - X1 + val DY12 = Y1 - Y2 + val DY23 = Y2 - Y3 + val DY31 = Y3 - Y1 + val FDX12 = DX12 shl 4 + val FDX23 = DX23 shl 4 + val FDX31 = DX31 shl 4 + val FDY12 = DY12 shl 4 + val FDY23 = DY23 shl 4 + val FDY31 = DY31 shl 4 + var minx = min(X1, X2, X3) + 0xF shr 4 + var maxx = max(X1, X2, X3) + 0xF shr 4 + var miny = min(Y1, Y2, Y3) + 0xF shr 4 + var maxy = max(Y1, Y2, Y3) + 0xF shr 4 + if (miny < 0) { + miny = 0 + } + if (minx < 0) { + minx = 0 + } + if (maxx > w) { + maxx = w + } + if (maxy > h) { + maxy = h + } + var off = miny * w + var C1 = DY12 * X1 - DX12 * Y1 + var C2 = DY23 * X2 - DX23 * Y2 + var C3 = DY31 * X3 - DX31 * Y3 + if (DY12 < 0 || DY12 == 0 && DX12 > 0) { + C1++ + } + if (DY23 < 0 || DY23 == 0 && DX23 > 0) { + C2++ + } + if ((DY31 < 0 || DY31 == 0) && DX31 > 0) { + C3++ + } + var CY1 = C1 + DX12 * (miny shl 4) - DY12 * (minx shl 4) + var CY2 = C2 + DX23 * (miny shl 4) - DY23 * (minx shl 4) + var CY3 = C3 + DX31 * (miny shl 4) - DY31 * (minx shl 4) + for (y in miny until maxy) { + var CX1 = CY1 + var CX2 = CY2 + var CX3 = CY3 + val p = zoff + dy * y + for (x in minx until maxx) { + if (CX1 > 0 && CX2 > 0 && CX3 > 0) { + val point = x + off + val zval = p + dx * x + if (zbuff[point] > zval) { + zbuff[point] = zval + img[point] = color + } + } + CX1 -= FDY12 + CX2 -= FDY23 + CX3 -= FDY31 + } + CY1 += FDX12 + CY2 += FDX23 + CY3 += FDX31 + off += w + } + } + + + fun trianglePhong( + zbuff: FloatArray, img: IntArray, + h3: Float, b3: Float, + h2: Float, b2: Float, + h1: Float, b1: Float, + sat: Float, + w: Int, h: Int, + fx3: Float, fy3: Float, fz3: Float, + fx2: Float, fy2: Float, fz2: Float, + fx1: Float, fy1: Float, fz1: Float + ) { + var h2 = h2 + var b2 = b2 + var h1 = h1 + var b1 = b1 + var fx2 = fx2 + var fy2 = fy2 + var fz2 = fz2 + var fx1 = fx1 + var fy1 = fy1 + var fz1 = fz1 + if ((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2) < 0) { + val tmpx = fx1 + val tmpy = fy1 + val tmpz = fz1 + fx1 = fx2 + fy1 = fy2 + fz1 = fz2 + fx2 = tmpx + fy2 = tmpy + fz2 = tmpz + val tmph = h1 + val tmpb = b1 + h1 = h2 + b1 = b2 + h2 = tmph + b2 = tmpb + } + // using maxmima + // string(solve([x1*dx+y1*dy+zoff=z1,x2*dx+y2*dy+zoff=z2,x3*dx+y3*dy+zoff=z3],[dx,dy,zoff])); + val d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + ((fx2 - fx3) + * fy1)) + if (d == 0.0f) { + return + } + val dx = (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + ((fy2 - fy3) + * fz1)) / d).toFloat() + val dy = ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + ((fx2 - fx3) + * fz1)) / d).toFloat() + val zoff = ((fx1 * (fy3 * fz2 - fy2 * fz3) + (fy1 + * (fx2 * fz3 - fx3 * fz2)) + (fx3 * fy2 - fx2 * fy3) * fz1) / d).toFloat() + val dhx = (-(fy1 * (h3 - h2) - fy2 * h3 + fy3 * h2 + ((fy2 - fy3) + * h1)) / d).toFloat() + val dhy = ((fx1 * (h3 - h2) - fx2 * h3 + fx3 * h2 + ((fx2 - fx3) + * h1)) / d).toFloat() + val hoff = ((fx1 * (fy3 * h2 - fy2 * h3) + (fy1 + * (fx2 * h3 - fx3 * h2)) + (fx3 * fy2 - fx2 * fy3) * h1) / d).toFloat() + val dbx = (-(fy1 * (b3 - b2) - fy2 * b3 + fy3 * b2 + ((fy2 - fy3) + * b1)) / d).toFloat() + val dby = ((fx1 * (b3 - b2) - fx2 * b3 + fx3 * b2 + ((fx2 - fx3) + * b1)) / d).toFloat() + val boff = ((fx1 * (fy3 * b2 - fy2 * b3) + (fy1 + * (fx2 * b3 - fx3 * b2)) + (fx3 * fy2 - fx2 * fy3) * b1) / d).toFloat() + + // 28.4 fixed-point coordinates + val Y1 = (16.0f * fy1 + .5f).toInt() + val Y2 = (16.0f * fy2 + .5f).toInt() + val Y3 = (16.0f * fy3 + .5f).toInt() + val X1 = (16.0f * fx1 + .5f).toInt() + val X2 = (16.0f * fx2 + .5f).toInt() + val X3 = (16.0f * fx3 + .5f).toInt() + val DX12 = X1 - X2 + val DX23 = X2 - X3 + val DX31 = X3 - X1 + val DY12 = Y1 - Y2 + val DY23 = Y2 - Y3 + val DY31 = Y3 - Y1 + val FDX12 = DX12 shl 4 + val FDX23 = DX23 shl 4 + val FDX31 = DX31 shl 4 + val FDY12 = DY12 shl 4 + val FDY23 = DY23 shl 4 + val FDY31 = DY31 shl 4 + var minx = min(X1, X2, X3) + 0xF shr 4 + var maxx = max(X1, X2, X3) + 0xF shr 4 + var miny = min(Y1, Y2, Y3) + 0xF shr 4 + var maxy = max(Y1, Y2, Y3) + 0xF shr 4 + if (miny < 0) { + miny = 0 + } + if (minx < 0) { + minx = 0 + } + if (maxx > w) { + maxx = w + } + if (maxy > h) { + maxy = h + } + var off = miny * w + var C1 = DY12 * X1 - DX12 * Y1 + var C2 = DY23 * X2 - DX23 * Y2 + var C3 = DY31 * X3 - DX31 * Y3 + if (DY12 < 0 || DY12 == 0 && DX12 > 0) { + C1++ + } + if (DY23 < 0 || DY23 == 0 && DX23 > 0) { + C2++ + } + if (DY31 < 0 || DY31 == 0 && DX31 > 0) { + C3++ + } + var CY1 = C1 + DX12 * (miny shl 4) - DY12 * (minx shl 4) + var CY2 = C2 + DX23 * (miny shl 4) - DY23 * (minx shl 4) + var CY3 = C3 + DX31 * (miny shl 4) - DY31 * (minx shl 4) + for (y in miny until maxy) { + var CX1 = CY1 + var CX2 = CY2 + var CX3 = CY3 + val p = zoff + dy * y + val ph = hoff + dhy * y + val pb = boff + dby * y + for (x in minx until maxx) { + if (CX1 > 0 && CX2 > 0 && CX3 > 0) { + val point = x + off + val zval = p + dx * x + val hue = ph + dhx * x + val bright = pb + dbx * x + if (zbuff[point] > zval) { + zbuff[point] = zval + img[point] = hsvToRgb(hue, sat, bright) + } + } + CX1 -= FDY12 + CX2 -= FDY23 + CX3 -= FDY31 + } + CY1 += FDX12 + CY2 += FDX23 + CY3 += FDX31 + off += w + } + } + + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/VectorUtil.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/VectorUtil.kt new file mode 100644 index 00000000..c80bda4a --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/VectorUtil.kt @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 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 com.google.constraintlayout.ext.graph3d + +import java.text.DecimalFormat + +/** + * A few utilities for vector calculations. + */ +object VectorUtil { + fun sub(a: DoubleArray?, b: DoubleArray?, out: DoubleArray?) { + out!![0] = a!![0] - b!![0] + out[1] = a[1] - b[1] + out[2] = a[2] - b[2] + } + + fun mult(a: DoubleArray?, b: Double, out: DoubleArray?) { + out!![0] = a!![0] * b + out[1] = a[1] * b + out[2] = a[2] * b + } + + fun dot(a: DoubleArray, b: DoubleArray): Double { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + } + + fun norm(a: DoubleArray?): Double { + return Math.sqrt(a!![0] * a[0] + a[1] * a[1] + a[2] * a[2]) + } + + fun norm(a: FloatArray?): Double { + return Math.sqrt((a!![0] * a[0] + a[1] * a[1] + a[2] * a[2]).toDouble()) + } + + fun cross(a: DoubleArray, b: DoubleArray?, out: DoubleArray?) { + val out0 = a[1] * b!![2] - b[1] * a[2] + val out1 = a[2] * b[0] - b[2] * a[0] + val out2 = a[0] * b[1] - b[0] * a[1] + out!![0] = out0 + out[1] = out1 + out[2] = out2 + } + + fun normalize(a: DoubleArray?) { + val norm = norm(a) + a!![0] /= norm + a!![1] /= norm + a!![2] /= norm + } + + fun normalize(a: FloatArray) { + val norm = norm(a).toFloat() + a[0] /= norm + a[1] /= norm + a[2] /= norm + } + + fun add( + a: DoubleArray, b: DoubleArray, + out: DoubleArray + ) { + out[0] = a[0] + b[0] + out[1] = a[1] + b[1] + out[2] = a[2] + b[2] + } + + fun madd( + a: DoubleArray?, x: Double, b: DoubleArray?, + out: DoubleArray? + ) { + out!![0] = x * a!![0] + b!![0] + out[1] = x * a[1] + b[1] + out[2] = x * a[2] + b[2] + } + + @JvmStatic + fun triangleNormal(vert: FloatArray, p1: Int, p2: Int, p3: Int, norm: FloatArray?) { + val x1 = vert[p2] - vert[p1] + val y1 = vert[p2 + 1] - vert[p1 + 1] + val z1 = vert[p2 + 2] - vert[p1 + 2] + val x2 = vert[p3] - vert[p1] + val y2 = vert[p3 + 1] - vert[p1 + 1] + val z2 = vert[p3 + 2] - vert[p1 + 2] + cross(x1, y1, z1, x2, y2, z2, norm) + val n = norm(norm).toFloat() + norm!![0] /= n + norm!![1] /= n + norm!![2] /= n + } + + fun dot(a: FloatArray?, b: FloatArray?): Float { + return a!![0] * b!![0] + a[1] * b[1] + a[2] * b[2] + } + + fun dot(a: FloatArray, offset: Int, b: FloatArray?): Float { + return a[offset] * b!![0] + a[1 + offset] * b[1] + a[2 + offset] * b[2] + } + + fun cross(a0: Float, a1: Float, a2: Float, b0: Float, b1: Float, b2: Float, out: FloatArray?) { + val out0 = a1 * b2 - b1 * a2 + val out1 = a2 * b0 - b2 * a0 + val out2 = a0 * b1 - b0 * a1 + out!![0] = out0 + out[1] = out1 + out[2] = out2 + } + + private fun trim(s: String): String { + return s.substring(s.length - 7) + } + + fun vecToString(light: FloatArray): String { + val df = DecimalFormat(" ##0.000") + var str = "[" + for (i in 0..2) { + if (java.lang.Float.isNaN(light[i])) { + str += (if (i == 0) "" else " , ") + trim(" NAN") + continue + } + str += (if (i == 0) "" else " , ") + trim(df.format(light[i].toDouble())) + } + return "$str]" + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/ViewMatrix.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/ViewMatrix.kt new file mode 100644 index 00000000..5b9252e7 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/ViewMatrix.kt @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2020 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 com.google.constraintlayout.ext.graph3d + +import java.text.DecimalFormat +import java.util.* + +/** + * This calculates the matrix that transforms triangles from world space to screen space. + */ +class ViewMatrix : Matrix() { + var lookPoint: DoubleArray? = null + var eyePoint: DoubleArray? = null + var upVector: DoubleArray? = null + var screenWidth = 0.0 + var mScreenDim: IntArray? = null + var mTmp1 = DoubleArray(3) + override fun print() { + println("mLookPoint :" + toStr(lookPoint)) + println("mEyePoint :" + toStr(eyePoint)) + println("mUpVector :" + toStr(upVector)) + println("mScreenWidth:" + toStr(screenWidth)) + println("mScreenDim :[" + mScreenDim!![0] + "," + mScreenDim!![1] + "]") + } + + fun setScreenDim(x: Int, y: Int) { + mScreenDim = intArrayOf(x, y) + } + + fun makeUnit() {} + fun fixUpPoint() { + val zv = doubleArrayOf( + eyePoint!![0] - lookPoint!![0], + eyePoint!![1] - lookPoint!![1], + eyePoint!![2] - lookPoint!![2] + ) + VectorUtil.normalize(zv) + val rv = DoubleArray(3) + VectorUtil.cross(zv, upVector, rv) + VectorUtil.cross(zv, rv, upVector) + VectorUtil.normalize(upVector) + VectorUtil.mult(upVector, -1.0, upVector) + } + + fun calcMatrix() { + if (mScreenDim == null) { + return + } + val scale = screenWidth / mScreenDim!![0] + val zv = doubleArrayOf( + lookPoint!![0] - eyePoint!![0], + lookPoint!![1] - eyePoint!![1], + lookPoint!![2] - eyePoint!![2] + ) + VectorUtil.normalize(zv) + val m = DoubleArray(16) + m[2] = zv[0] * scale + m[6] = zv[1] * scale + m[10] = zv[2] * scale + m[14] = 0.0 + calcRight(zv, upVector, zv) + m[0] = zv[0] * scale + m[4] = zv[1] * scale + m[8] = zv[2] * scale + m[12] = 0.0 + m[1] = -upVector!![0] * scale + m[5] = -upVector!![1] * scale + m[9] = -upVector!![2] * scale + m[13] = 0.0 + val sw = mScreenDim!![0] / 2 - 0.5 + val sh = mScreenDim!![1] / 2 - 0.5 + val sz = -0.5 + m[3] = eyePoint!![0] - (m[0] * sw + m[1] * sh + m[2] * sz) + m[7] = eyePoint!![1] - (m[4] * sw + m[5] * sh + m[6] * sz) + m[11] = eyePoint!![2] - (m[8] * sw + m[9] * sh + m[10] * sz) + m[15] = 1.0 + this.m = m + } + + private fun calcLook(tri: Object3D, voxelDim: FloatArray, w: Int, h: Int) { + var minx = Float.MAX_VALUE + var miny = Float.MAX_VALUE + var minz = Float.MAX_VALUE + var maxx = -Float.MAX_VALUE + var maxy = -Float.MAX_VALUE + var maxz = -Float.MAX_VALUE + var i = 0 + while (i < tri.vert.size) { + maxx = Math.max(tri.vert[i], maxx) + minx = Math.min(tri.vert[i], minx) + maxy = Math.max(tri.vert[i + 1], maxy) + miny = Math.min(tri.vert[i + 1], miny) + maxz = Math.max(tri.vert[i + 2], maxz) + minz = Math.min(tri.vert[i + 2], minz) + i += 3 + } + lookPoint = doubleArrayOf( + (voxelDim[0] * (maxx + minx) / 2).toDouble(), + (voxelDim[1] * (maxy + miny) / 2).toDouble(), + (voxelDim[2] * (maxz + minz) / 2).toDouble() + ) + screenWidth = (Math.max( + voxelDim[0] * (maxx - minx), Math.max( + voxelDim[1] * (maxy - miny), voxelDim[2] * (maxz - minz) + ) + ) * 2).toDouble() + } + + private fun calcLook(triW: Object3D, w: Int, h: Int) { + var minx = Float.MAX_VALUE + var miny = Float.MAX_VALUE + var minz = Float.MAX_VALUE + var maxx = -Float.MAX_VALUE + var maxy = -Float.MAX_VALUE + var maxz = -Float.MAX_VALUE + var i = 0 + while (i < triW.vert.size) { + maxx = Math.max(triW.vert[i], maxx) + minx = Math.min(triW.vert[i], minx) + maxy = Math.max(triW.vert[i + 1], maxy) + miny = Math.min(triW.vert[i + 1], miny) + maxz = Math.max(triW.vert[i + 2], maxz) + minz = Math.min(triW.vert[i + 2], minz) + i += 3 + } + lookPoint = doubleArrayOf( + ((maxx + minx) / 2).toDouble(), + ((maxy + miny) / 2).toDouble(), + ((maxz + minz) / 2).toDouble() + ) + screenWidth = Math.max(maxx - minx, Math.max(maxy - miny, maxz - minz)).toDouble() + } + + fun look(dir: Char, tri: Object3D, voxelDim: FloatArray?, w: Int, h: Int) { + calcLook(tri, w, h) + var dx = dir.code shr 4 and 0xF + var dy = dir.code shr 8 and 0xF + var dz = dir.code shr 0 and 0xF + if (dx > 1) { + dx = -1 + } + if (dy > 1) { + dy = -1 + } + if (dz > 1) { + dz = -1 + } + eyePoint = doubleArrayOf( + lookPoint!![0] + 2 * screenWidth * dx, + lookPoint!![1] + 2 * screenWidth * dy, + lookPoint!![2] + 2 * screenWidth * dz + ) + val zv = doubleArrayOf(-dx.toDouble(), -dy.toDouble(), -dz.toDouble()) + val rv = + doubleArrayOf(if (dx == 0) 1.0 else 0.0, if (dx == 0) 0.0 else 1.0 , 0.0) + val up = DoubleArray(3) + VectorUtil.norm(zv) + VectorUtil.norm(rv) + VectorUtil.cross(zv, rv, up) + VectorUtil.cross(zv, up, rv) + VectorUtil.cross(zv, rv, up) + upVector = up + mScreenDim = intArrayOf(w, h) + calcMatrix() + } + + fun lookAt(tri: Object3D, voxelDim: FloatArray, w: Int, h: Int) { + calcLook(tri, voxelDim, w, h) + eyePoint = doubleArrayOf( + lookPoint!![0] + screenWidth, + lookPoint!![1] + screenWidth, + lookPoint!![2] + screenWidth + ) + val zv = doubleArrayOf(-1.0, -1.0, -1.0) + val rv = doubleArrayOf(1.0, 1.0, 0.0) + val up = DoubleArray(3) + VectorUtil.norm(zv) + VectorUtil.norm(rv) + VectorUtil.cross(zv, rv, up) + VectorUtil.cross(zv, up, rv) + VectorUtil.cross(zv, rv, up) + upVector = up + mScreenDim = intArrayOf(w, h) + calcMatrix() + } + + var mStartx = 0f + var mStarty = 0f + var mPanStartX = Float.NaN + var mPanStartY = Float.NaN + var mStartMatrix: Matrix? = null + var mStartV = DoubleArray(3) + var mMoveToV = DoubleArray(3) + lateinit var mStartEyePoint: DoubleArray + lateinit var mStartUpVector: DoubleArray + var mQ = Quaternion(0.0, 0.0, 0.0, 0.0) + fun trackBallUP(x: Float, y: Float) {} + fun trackBallDown(x: Float, y: Float) { + mStartx = x + mStarty = y + ballToVec(x, y, mStartV) + mStartEyePoint = Arrays.copyOf(eyePoint, m.size) + mStartUpVector = Arrays.copyOf(upVector, m.size) + mStartMatrix = Matrix(this) + mStartMatrix!!.makeRotation() + } + + fun trackBallMove(x: Float, y: Float) { + if (mStartx == x && mStarty == y) { + return + } + ballToVec(x, y, mMoveToV) + val angle: Double = Quaternion.Companion.calcAngle(mStartV, mMoveToV) + var axis: DoubleArray? = Quaternion.Companion.calcAxis(mStartV, mMoveToV) + axis = mStartMatrix!!.vecmult(axis) + mQ[angle] = axis + VectorUtil.sub(lookPoint, mStartEyePoint, eyePoint) + eyePoint = mQ.rotateVec(eyePoint) + upVector = mQ.rotateVec(mStartUpVector) + VectorUtil.sub(lookPoint, eyePoint, eyePoint) + calcMatrix() + } + + fun panDown(x: Float, y: Float) { + mPanStartX = x + mPanStartY = y + } + + fun panMove(x: Float, y: Float) { + val scale = screenWidth / mScreenDim!![0] + if (java.lang.Float.isNaN(mPanStartX)) { + mPanStartX = x + mPanStartY = y + } + val dx = scale * (x - mPanStartX) + val dy = scale * (y - mPanStartY) + VectorUtil.sub(eyePoint, lookPoint, mTmp1) + VectorUtil.normalize(mTmp1) + VectorUtil.cross(mTmp1, upVector, mTmp1) + VectorUtil.madd(mTmp1, dx, eyePoint, eyePoint) + VectorUtil.madd(mTmp1, dx, lookPoint, lookPoint) + VectorUtil.madd(upVector, dy, eyePoint, eyePoint) + VectorUtil.madd(upVector, dy, lookPoint, lookPoint) + mPanStartY = y + mPanStartX = x + calcMatrix() + } + + fun panUP() { + mPanStartX = Float.NaN + mPanStartY = Float.NaN + } + + fun ballToVec(x: Float, y: Float, v: DoubleArray) { + val ballRadius = Math.min(mScreenDim!![0], mScreenDim!![1]) * .4f + val cx = mScreenDim!![0] / 2.0 + val cy = mScreenDim!![1] / 2.0 + var dx = (cx - x) / ballRadius + var dy = (cy - y) / ballRadius + var scale = dx * dx + dy * dy + if (scale > 1) { + scale = Math.sqrt(scale) + dx = dx / scale + dy = dy / scale + } + val dz = Math.sqrt(Math.abs(1 - (dx * dx + dy * dy))) + v[0] = dx + v[1] = dy + v[2] = dz + VectorUtil.normalize(v) + } + + companion object { + const val UP_AT = 0x001.toChar() + const val DOWN_AT = 0x002.toChar() + const val RIGHT_AT = 0x010.toChar() + const val LEFT_AT = 0x020.toChar() + const val FORWARD_AT = 0x100.toChar() + const val BEHIND_AT = 0x200.toChar() + private fun toStr(d: Double): String { + val s = " " + df.format(d) + return s.substring(s.length - 8) + } + + private fun toStr(d: DoubleArray?): String { + var s = "[" + for (i in d!!.indices) { + s += toStr(d[i]) + } + return "$s]" + } + + private val df = DecimalFormat("##0.000") + fun calcRight(a: DoubleArray, b: DoubleArray?, out: DoubleArray?) { + VectorUtil.cross(a, b, out) + } + + @JvmStatic + fun main(args: Array) { + val up = doubleArrayOf(0.0, 0.0, 1.0) + val look = doubleArrayOf(0.0, 0.0, 0.0) + val eye = doubleArrayOf(-10.0, 0.0, 0.0) + val v = ViewMatrix() + v.eyePoint = eye + v.lookPoint = look + v.upVector = up + v.screenWidth = 10.0 + v.setScreenDim(512, 512) + v.calcMatrix() + } + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/AxisBox.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/AxisBox.kt new file mode 100644 index 00000000..afcd837d --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/AxisBox.kt @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.graph3d.objects + +import android.support.composegraph3d.lib.Object3D +import android.support.composegraph3d.lib.Scene3D +import android.support.composegraph3d.lib.Scene3D.Companion.drawline +import android.support.composegraph3d.lib.Scene3D.Companion.hsvToRgb +import android.support.composegraph3d.lib.Scene3D.Companion.isBackface +import android.support.composegraph3d.lib.Scene3D.Companion.triangle +import android.support.composegraph3d.lib.VectorUtil.dot +import android.support.composegraph3d.lib.VectorUtil.triangleNormal + +/** + * Draws box along the axis + */ +class AxisBox : Object3D() { + var color = -0xefefdf + fun setRange(minX: Float, maxX: Float, minY: Float, maxY: Float, minZ: Float, maxZ: Float) { + mMinX = minX + mMaxX = maxX + mMinY = minY + mMaxY = maxY + mMinZ = minZ + mMaxZ = maxZ + buildBox() + } + + fun buildBox() { + vert = FloatArray(8 * 3) // cube 8 corners + tVert = FloatArray(vert.size) + for (i in 0..7) { + vert[i * 3] = if (i and 1 == 0) mMinX else mMaxX // X + vert[i * 3 + 1] = if (i shr 1 and 1 == 0) mMinY else mMaxY // Y + vert[i * 3 + 2] = if (i shr 2 and 1 == 0) mMinZ else mMaxZ // Z + } + index = IntArray(6 * 2 * 3) // 6 sides x 2 triangles x 3 points per triangle + val sides = intArrayOf( // pattern of clockwise triangles around cube + 0, 2, 1, 3, 1, 2, + 0, 1, 4, 5, 4, 1, + 0, 4, 2, 6, 2, 4, + 7, 6, 5, 4, 5, 6, + 7, 3, 6, 2, 6, 3, + 7, 5, 3, 1, 3, 5 + ) + index = IntArray(sides.size) + for (i in sides.indices) { + index[i] = sides[i] * 3 + } + } + + fun render_old(s: Scene3D?, zbuff: FloatArray?, img: IntArray?, w: Int, h: Int) { + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + val height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3 + val `val` = (255 * Math.abs(height)).toInt() + drawline( + zbuff!!, img!!, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f + ) + i += 3 + } + } + + override fun render(s: Scene3D, zbuff: FloatArray, img: IntArray, w: Int, h: Int) { + // raster_color(s, zbuff, img, w, h) + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + val front = isBackface( + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] + ) + if (front) { + drawline( + zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f + ) + drawline( + zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f + ) + i += 3 + continue + } + drawline( + zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f + ) + drawTicks( + zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f, + tVert[p3] - tVert[p1], tVert[p3 + 1] - tVert[p1 + 1], tVert[p3 + 2] - tVert[p1 + 2] + ) + drawline( + zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f + ) + drawTicks( + zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f, + tVert[p2] - tVert[p1], tVert[p2 + 1] - tVert[p1 + 1], tVert[p2 + 2] - tVert[p1 + 2] + ) + i += 3 + } + } + + var screen = floatArrayOf(0f, 0f, -1f) + + init { + type = 1 + } + + override fun raster_color(s: Scene3D, zbuff: FloatArray, img: IntArray, w: Int, h: Int) { + var i = 0 + while (i < index.size) { + val p1 = index[i] + val p2 = index[i + 1] + val p3 = index[i + 2] + val back = isBackface( + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] + ) + if (back) { + i += 3 + continue + } + triangleNormal(tVert, p1, p3, p2, s.tmpVec) + val ss = dot(s.tmpVec, screen) + val defuse = dot(s.tmpVec, s.mTransformedLight) + val ambient = 0.5f + val bright = Math.min(Math.max(0f, defuse + ambient), 1f) + val hue = 0.4f + val sat = 0.1f + val col = hsvToRgb(hue, sat, bright) + triangle( + zbuff, img, col, w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2] + ) + i += 3 + } + } + + companion object { + fun drawTicks( + zbuff: FloatArray?, img: IntArray?, color: Int, w: Int, h: Int, + p1x: Float, p1y: Float, p1z: Float, + p2x: Float, p2y: Float, p2z: Float, + nx: Float, ny: Float, nz: Float + ) { + val tx = nx / 10 + val ty = ny / 10 + val tz = nz / 10 + var f = 0f + while (f <= 1) { + val px = p1x + f * (p2x - p1x) + val py = p1y + f * (p2y - p1y) + val pz = p1z + f * (p2z - p1z) + drawline( + zbuff!!, img!!, color, w, h, + px, py, pz - 0.01f, + px + tx, py + ty, pz + tz - 0.01f + ) + f += 0.1f + } + } + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/Surface3D.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/Surface3D.kt new file mode 100644 index 00000000..8115426e --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/graph3d/objects/Surface3D.kt @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.graph3d.objects + +import android.support.composegraph3d.lib.Object3D + +/** + * Plots a surface based on Z = f(X,Y) + */ +class Surface3D(private val mFunction: Function) : Object3D() { + private val mZoomZ = 1f + var mSize = 100 + fun setRange(minX: Float, maxX: Float, minY: Float, maxY: Float, minZ: Float, maxZ: Float) { + mMinX = minX + mMaxX = maxX + mMinY = minY + mMaxY = maxY + mMinZ = minZ + mMaxZ = maxZ + computeSurface(java.lang.Float.isNaN(mMinZ)) + } + + fun setArraySize(size: Int) { + mSize = size + computeSurface(false) + } + + interface Function { + fun eval(x: Float, y: Float): Float + } + + fun computeSurface(resetZ: Boolean) { + val n = (mSize + 1) * (mSize + 1) + makeVert(n) + makeIndexes(mSize * mSize * 2) + calcSurface(resetZ) + } + + fun calcSurface(resetZ: Boolean) { + val min_x = mMinX + val max_x = mMaxX + val min_y = mMinY + val max_y = mMaxY + var min_z = Float.MAX_VALUE + var max_z = -Float.MAX_VALUE + var count = 0 + for (iy in 0..mSize) { + val y = min_y + iy * (max_y - min_y) / mSize + for (ix in 0..mSize) { + val x = min_x + ix * (max_x - min_x) / mSize + val delta = 0.001f + var dx = (mFunction.eval(x + delta, y) - mFunction.eval(x - delta, y)) / (2 * delta) + var dy = (mFunction.eval(x, y + delta) - mFunction.eval(x, y - delta)) / (2 * delta) + var dz = 1f + val norm = Math.sqrt((dz * dz + dx * dx + dy * dy).toDouble()).toFloat() + dx /= norm + dy /= norm + dz /= norm + normal[count] = dx + vert[count++] = x + normal[count] = dy + vert[count++] = y + normal[count] = -dz + var z = mFunction.eval(x, y) + if (java.lang.Float.isNaN(z) || java.lang.Float.isInfinite(z)) { + val epslonX = 0.000005232f + val epslonY = 0.00000898f + z = mFunction.eval(x + epslonX, y + epslonY) + } + vert[count++] = z + if (java.lang.Float.isNaN(z)) { + continue + } + if (java.lang.Float.isInfinite(z)) { + continue + } + min_z = Math.min(z, min_z) + max_z = Math.max(z, max_z) + } + if (resetZ) { + mMinZ = min_z + mMaxZ = max_z + } + } + // normalize range in z + val xrange = mMaxX - mMinX + val yrange = mMaxY - mMinY + val zrange = mMaxZ - mMinZ + if (zrange != 0f && resetZ) { + val xyrange = (xrange + yrange) / 2 + val scalez = xyrange / zrange + var i = 0 + while (i < vert.size) { + var z = vert[i + 2] + if (java.lang.Float.isNaN(z) || java.lang.Float.isInfinite(z)) { + z = if (i > 3) { + vert[i - 1] + } else { + vert[i + 5] + } + } + vert[i + 2] = z * scalez * mZoomZ + i += 3 + } + if (resetZ) { + mMinZ *= scalez + mMaxZ *= scalez + } + } + count = 0 + for (iy in 0 until mSize) { + for (ix in 0 until mSize) { + val p1 = 3 * (ix + iy * (mSize + 1)) + val p2 = 3 * (1 + ix + iy * (mSize + 1)) + val p3 = 3 * (ix + (iy + 1) * (mSize + 1)) + val p4 = 3 * (1 + ix + (iy + 1) * (mSize + 1)) + index[count++] = p1 + index[count++] = p2 + index[count++] = p3 + index[count++] = p4 + index[count++] = p3 + index[count++] = p2 + } + } + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCompose.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCompose.kt new file mode 100644 index 00000000..580fcc16 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCompose.kt @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.variableplot; + +import androidx.compose.foundation.Canvas +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.neverEqualPolicy +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.input.pointer.motionEventSpy +import androidx.compose.ui.layout.onPlaced +import androidx.compose.ui.node.Ref +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + + + +fun vpSend(channel:String, value:Float) { + Vp.send(channel,value) +} + +fun vpSend(channel:String,time:Long, value:Float) { + Vp.send(channel,time,value) +} + +fun vpFps(channel: String){ + Vp.fps(channel); +} + +@OptIn(ExperimentalComposeUiApi::class) +@Composable +fun VpGraph( + modifier: Modifier = Modifier, + vararg channels: String, +) { + val width = remember { + Ref().apply { 0 } + } + val height = remember { + Ref().apply { 0 } + } + + val invalidator = remember { mutableStateOf(Unit, neverEqualPolicy()) } + + val scope = rememberCoroutineScope() + + val uiDelegate = remember { + object : UiDelegate{ + override fun post(runnable: Runnable?): Boolean { + scope.launch { + runnable?.run() + } + return true + } + + override fun postDelayed(runnable: Runnable?, delayMillis: Long): Boolean { + scope.launch { + delay(delayMillis) + runnable?.run() + } + return true + } + + override fun invalidate() { + invalidator.value = Unit + } + + override fun getWidth(): Int { + return width.value ?: 0 + } + + override fun getHeight(): Int { + return height.value ?: 0 + } + } + } + + val graphCore = remember { + VpGraphCore(uiDelegate) + } + + LaunchedEffect(channels) { + channels.forEach { + graphCore.addChannel(it) + } + } +val c = Color(0x323) + Canvas( + modifier = modifier.clipToBounds() + .onPlaced { + width.value = it.size.width + height.value = it.size.height + } + .motionEventSpy { + graphCore.onTouchEvent(it) + } + , + onDraw = { + invalidator.value + graphCore.onDraw(this.drawContext.canvas.nativeCanvas) + } + ) +} diff --git a/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCore.kt b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCore.kt new file mode 100644 index 00000000..1b65e527 --- /dev/null +++ b/constraintlayout/extensionLibrary/Compose/src/kotlin/com/google/constraintlayout/ext/variableplot/VpGraphCore.kt @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.variableplot; + +import android.graphics.* +import android.util.Log +import android.view.MotionEvent +import java.text.DecimalFormat +import kotlin.math.floor + +class VpGraphCore(private val mUiDelegate: UiDelegate) { + private var mPlots = ArrayList() + private val mTime = LongArray(MAX_BUFF) + private val mValue = FloatArray(MAX_BUFF) + var duration = 10f // seconds + var mMinY = 0f + var mMaxY = 1f + var mMinX = 0f + var mMaxX = duration + mMinX + var axisLeft = 100f + var axisTop = 100f + var axisRight = 100f + var axisBottom = 100f + var mAxisPaint = Paint() + var mLinePaint = Paint() + var mGridPaint = Paint() + var mBounds = Rect() + var start: Long = -1 // show the latest; + var mGraphSelfFps = false + var sampleDelay = 15 // ms between samples. + private var mLiveSample = true + private var mStartTime: Long = 0 + private var mLineX = Float.NaN + var debug = false + + internal class Data(var mTitle: String) { + var mX = FloatArray(MAX_BUFF) + var mY = FloatArray(MAX_BUFF) + var paint = Paint() + var path = Path() + var mLength: Int + var lastLabelYPos = Float.NaN + + init { + mLength = -1 + paint.style = Paint.Style.STROKE + } + + fun plot(canvas: Canvas, graph: VpGraphCore, w: Int, h: Int) { + path.reset() + val scaleX = graph.getScaleX(w, h) + val scaleY = graph.getScaleY(w, h) + val offX = graph.getOffsetX(w, h) + val offY = graph.getOffsetY(w, h) + var first = true + for (i in 0 until mLength) { + if ((i == mLength - 1 || mX[i + 1] >= graph.mMinX) && mX[i] <= graph.mMaxX) { + val x = mX[i] * scaleX + offX + val y = mY[i] * scaleY + offY + if (first) { + path.moveTo(x, y) + first = false + } else { + path.lineTo(x, y) + } + } + } + canvas.drawPath(path, paint) + } + + fun findClosestX(x: Float): Int { + var low = 0 + var high = mLength - 1 + var pos = -1 + while (low <= high) { + pos = low + (high - low) / 2 + if (mX[pos] == x) return pos + if (mX[pos] < x) low = pos + 1 else high = pos - 1 + } + return pos + } + } + + fun init() { + mUiDelegate.post { listenToChannels() } + mAxisPaint.textSize = 32f + mAxisPaint.strokeWidth = 3f + mAxisPaint.color = Color.BLUE + mGridPaint.textSize = 32f + mGridPaint.strokeWidth = 1f + mLinePaint.color = Color.RED + mLinePaint.strokeWidth = 3f + mLinePaint.textSize = 64f + } + + var mDownX = 0f + fun onTouchEvent(event: MotionEvent): Boolean { + val action = event.action and MotionEvent.ACTION_MASK + val count = event.pointerCount + when (action) { + MotionEvent.ACTION_MOVE -> if (count == 2) { + var drag = event.getX(0) + event.getX(1) + drag = (drag + mDownX) / 2 + mStartTime += (drag / getScaleX(mUiDelegate.width, mUiDelegate.height)).toLong() + listenToChannels() + Log.v("Main", ">>>> drag $drag") + mLineX = Float.NaN + } else { + mLineX = event.x + Log.v("Main", ">>>> ACTION_MOVE " + event.x + " ") + mUiDelegate.invalidate() + } + MotionEvent.ACTION_DOWN -> if (count == 2) { + mLiveSample = false + mDownX = event.getX(0) + event.getX(1) + Log.v("Main", "$count>>>> drag on$mDownX ") + mLineX = Float.NaN + } else { + mLineX = event.x + Log.v("Main", count.toString() + ">>>> ACTION_DOWN " + event.x) + mUiDelegate.invalidate() + } + MotionEvent.ACTION_UP -> { + Log.v("Main", ">>>> ACTION_UP " + event.x) + mLineX = Float.NaN + for (mPlot in mPlots) { + mPlot.lastLabelYPos = Float.NaN + } + mUiDelegate.invalidate() + } + MotionEvent.ACTION_POINTER_DOWN -> if (count == 2) { + mLiveSample = false + mDownX = event.getX(0) + event.getX(1) + Log.v("Main", "$count>>>> ACTION_POINTER_DOWN$mDownX $mLiveSample") + mLineX = Float.NaN + } + MotionEvent.ACTION_POINTER_UP -> { + Log.v("Main", ">>>> ACTION_POINTER_UP" + event.x) + if (event.eventTime - event.downTime < 400) { + Log.v("Main", ">>>> false") + mLiveSample = true + listenToChannels() + } + Log.v("Main", ">>>> def " + event.eventTime) + } + else -> Log.v("Main", ">>>> def " + event.eventTime) + } + return true + //return super.onTouchEvent(event); + } + + fun addChannel(str: String) { + mPlots.add(Data(str)) + } + + fun setGraphFPS(on: Boolean) { + mGraphSelfFps = on + if (on) { + mPlots.add(Data(FPS_STRING)) + // mPlots.add(new Data("read")); + } else { + var remove: Data? = null + for (i in mPlots.indices) { + if (mPlots[i].mTitle === FPS_STRING) { + remove = mPlots[i] + break + } + } + if (remove != null) { + mPlots.remove(remove) + } + } + } + + private fun listenToChannels() { + // Vp.fps("read"); + if (mLiveSample) { + listenLive() + return + } + val count = mPlots.size + var minX = Float.MAX_VALUE + var minY = Float.MAX_VALUE + var maxX = -Float.MAX_VALUE + var maxY = -Float.MAX_VALUE + for (i in 0 until count) { + val p = mPlots[i] + val channel = p.mTitle + p.mLength = Vp.getAfter(channel, mStartTime, mTime, mValue) + if (p.mLength == -1) { + continue + } + for (j in 0 until p.mLength) { + val x = (mTime[j] - mStartTime) * 1E-9f + p.mX[j] = x + minX = Math.min(x, minX) + maxX = Math.max(x, maxX) + val y = mValue[j] + minY = Math.min(y, minY) + maxY = Math.max(y, maxY) + p.mY[j] = y + } + Log.v("main", p.mTitle + " " + minX + " -> " + maxX) + } + minX = 0f + maxX = minX + duration + Log.v("main", "Total $minX -> $maxX") + updateDataRange(minX, maxX, minY, maxY) + mUiDelegate.invalidate() + } + + private fun listenLive() { + val count = mPlots.size + mStartTime = System.nanoTime() - (duration.toDouble() * 1000000000L).toLong() + var minX = Float.MAX_VALUE + var minY = Float.MAX_VALUE + var maxX = -Float.MAX_VALUE + var maxY = -Float.MAX_VALUE + for (i in 0 until count) { + val p = mPlots[i] + val channel = p.mTitle + p.mLength = Vp.getLatest(channel, mTime, mValue) + if (p.mLength == -1) { + continue + } + for (j in 0 until p.mLength) { + val x = (mTime[j] - mStartTime) * 1E-9f + p.mX[j] = x + minX = Math.min(x, minX) + maxX = Math.max(x, maxX) + val y = mValue[j] + minY = Math.min(y, minY) + maxY = Math.max(y, maxY) + p.mY[j] = y + } + } + if (minX == Float.MAX_VALUE || java.lang.Float.isNaN(minX)) { + updateDataRange(0f, 10f, -1f, 1f) + } else { + updateDataRange(minX, maxX, minY, maxY) + } + mUiDelegate.invalidate() + mUiDelegate.postDelayed({ listenToChannels() }, sampleDelay.toLong()) + } + + private fun updateDataRange(minX: Float, maxX: Float, minY: Float, maxY: Float) { + var minX = minX + minX = maxX - duration + // fast to expand slow to contract + val factor = 10f + mMaxY = if (mMaxY > maxY) (mMaxY + maxY) / 2 else (mMaxY * factor + maxY) / (factor + 1) + mMinY = if (mMinY < minY) (mMinY + minY) / 2 else (mMinY * factor + minY) / (factor + 1) + mMinX = (mMinX + minX) / 2 + mMaxX = duration + mMinX + } + + fun onDraw(canvas: Canvas) { + val w = mUiDelegate.width + val h = mUiDelegate.height + drawAxis(canvas, w, h) + drawGrid(canvas, w, h) + for (p in mPlots) { + p.plot(canvas, this, w, h) + } + if (mGraphSelfFps || debug) { + Vp.fps(FPS_STRING) + } + drawTouchLine(canvas, w, h) + } + + private fun drawGrid(canvas: Canvas, w: Int, h: Int) { + val ticksX = calcTick(w, (mMaxX - mMinX).toDouble()) + val ticksY = calcTick(h, (mMaxY - mMinY).toDouble()) + val minX = (ticksX * Math.ceil((mMinX + ticksX / 100) / ticksX)).toFloat() + val maxX = (ticksX * Math.floor(mMaxX / ticksX)).toFloat() + val scaleX = getScaleX(w, h) + val offX = getOffsetX(w, h) + var x = minX + var count = 0 + val txtPad = 4 + + while (x <= maxX) { + val xp = scaleX * x + offX + canvas.drawLine(xp, axisTop, xp, h - axisBottom, mGridPaint) + x += ticksX.toFloat() + if (floor(x) == x) { + val str = df.format(x) + mAxisPaint.getTextBounds(str, 0, str.length, mBounds) + canvas.drawText( + str, + xp - mBounds.width() / 2, + h - axisBottom + txtPad + mBounds.height(), + mGridPaint + ) + } + count++ + } + val minY = (ticksY * Math.ceil((mMinY + ticksY / 100) / ticksY)).toFloat() + val maxY = (ticksY * Math.floor(mMaxY / ticksY)).toFloat() + val offY = getOffsetY(w, h) + val scaleY = getScaleY(w, h) + var y = minY + while (y <= maxY) { + val yp = scaleY * y + offY + canvas.drawLine(axisLeft, yp, w - axisRight, yp, mGridPaint) + if (count and 1 == 1 && y + ticksY < maxY) { + val str = df.format(y) + mAxisPaint.getTextBounds(str, 0, str.length, mBounds) + canvas.drawText(str, axisLeft - mBounds.width() - txtPad * 2, yp, mGridPaint) + } + count++ + y += ticksY.toFloat() + } + } + + var df = DecimalFormat("0.0") + + init { + init() + mAxisPaint.color = Color.BLACK + } + + fun drawTouchLine(canvas: Canvas, w: Int, h: Int) { + if (java.lang.Float.isNaN(mLineX)) { + return + } + if (mLineX < axisLeft) { + mLineX = axisLeft + } else if (mLineX > w - axisRight) { + mLineX = w - axisRight + } + val dataPos = (mLineX - getOffsetX(w, h)) / getScaleX(w, h) + val yOffset = getOffsetY(w, h) + val yScale = getScaleY(w, h) + val rad = 10f + canvas.drawLine(mLineX, axisTop, mLineX, h - axisBottom, mLinePaint) + var bottom_count = 0 + var top_count = 0 + val pad = 5 + val right = mLineX < w / 2 + val no_of_plots = mPlots.size + for (i in 0 until no_of_plots) { + val plot = mPlots[i] + val index = plot.findClosestX(dataPos) + if (index == -1) continue + val value = plot.mY[index] + val y = yScale * value + yOffset + canvas.drawRoundRect(mLineX - rad, y - rad, mLineX + rad, y + rad, rad, rad, mLinePaint) + val vString = plot.mTitle + ":" + df.format(value.toDouble()) + mLinePaint.getTextBounds(vString, 0, vString.length, mBounds) + var yPos = (w / 2).toFloat() + val gap = 60 + if (y > h / 2) { + yPos = y - gap + bottom_count++ + } else { + top_count++ + yPos = y + gap + } + val xPos = if (right) mLineX + gap else mLineX - gap + val minVGap = mBounds.height().toFloat() + var force = 0f + for (j in 0 until no_of_plots) { + if (i == j) { + continue + } + val yOther = mPlots[j].lastLabelYPos + val dist = Math.abs(plot.lastLabelYPos - yOther) + if (dist < minVGap * 2) { + val dir = Math.signum(yPos - yOther) + force = (if (dir > 0) minVGap else -minVGap) + minVGap / (0.1f + dist) + } + } + if (java.lang.Float.isNaN(plot.lastLabelYPos)) { + plot.lastLabelYPos = yPos + } else { + plot.lastLabelYPos = (plot.lastLabelYPos * 49 + yPos + force) / 50 + } + canvas.drawLine(mLineX, y, xPos, plot.lastLabelYPos, mLinePaint) + canvas.drawText( + vString, + if (right) xPos else xPos - mBounds.width() - pad, + plot.lastLabelYPos, + mLinePaint + ) + } + } + + fun drawAxis(canvas: Canvas, w: Int, h: Int) { + val txtPad = 4 + canvas.drawRGB(200, 230, 255) + canvas.drawLine(axisLeft, axisTop, axisLeft, h - axisBottom, mAxisPaint) + canvas.drawLine(axisLeft, h - axisBottom, w - axisRight, h - axisBottom, mAxisPaint) + val y0 = getOffsetY(w, h) + canvas.drawLine(axisLeft, y0, w - axisRight, y0, mAxisPaint) + var str = df.format(mMaxY.toDouble()) + mAxisPaint.getTextBounds(str, 0, str.length, mBounds) + canvas.drawText(str, axisLeft - mBounds.width() - txtPad, axisTop, mAxisPaint) + str = df.format(mMinY.toDouble()) + mAxisPaint.getTextBounds(str, 0, str.length, mBounds) + canvas.drawText(str, axisLeft - mBounds.width() - txtPad, h - axisBottom, mAxisPaint) + } + + fun getScaleX(w: Int, h: Int): Float { + val rangeX = mMaxX - mMinX + val graphSpanX = w - axisLeft - axisRight + return graphSpanX / rangeX + } + + fun getScaleY(w: Int, h: Int): Float { + val rangeY = mMaxY - mMinY + val graphSpanY = h - axisTop - axisBottom + return -graphSpanY / rangeY + } + + fun getOffsetX(w: Int, h: Int): Float { + return axisLeft - mMinX * getScaleX(w, h) + } + + fun getOffsetY(w: Int, h: Int): Float { + return h - axisBottom - mMinY * getScaleY(w, h) + } + + companion object { + private const val FPS_STRING = "onDraw" + const val MAX_BUFF = 2000 + fun calcTick(scr: Int, range: Double): Double { + val aprox_x_ticks = scr / 100 + var type = 1 + var best = Math.log10(range / aprox_x_ticks) + var n = Math.log10(range / (aprox_x_ticks * 2)) + if (fraction(n) < fraction(best)) { + best = n + type = 2 + } + n = Math.log10(range / (aprox_x_ticks * 5)) + if (fraction(n) < fraction(best)) { + best = n + type = 5 + } + return type * Math.pow(10.0, Math.floor(best)) + } + + fun fraction(x: Double): Double { + return x - Math.floor(x) + } + } +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Views/README.md b/constraintlayout/extensionLibrary/Views/README.md new file mode 100644 index 00000000..60d95a95 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/README.md @@ -0,0 +1 @@ +# ConstraintLayout Extension Library for Views diff --git a/constraintlayout/extensionLibrary/Views/build.gradle.kts b/constraintlayout/extensionLibrary/Views/build.gradle.kts new file mode 100644 index 00000000..1f3ab7dd --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/build.gradle.kts @@ -0,0 +1,130 @@ +/* + * Copyright 2023 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 + * + * 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. + */ +@file:Suppress("UnstableApiUsage") + +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.android.kotlin.get().pluginId) + id(libs.plugins.jetbrains.dokka.get().pluginId) + id(libs.plugins.gradle.metalava.get().pluginId) + id(libs.plugins.vanniktech.maven.publish.get().pluginId) +} + +kotlin { + explicitApi() +} + +android { + namespace = "com.google.accompanist.pager" + + compileSdk = 34 + + defaultConfig { + minSdk = 21 + // targetSdkVersion has no effect for libraries. This is only used for the test APK + targetSdk = 33 + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + buildFeatures { + buildConfig = false + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + lint { + textReport = true + textOutput = File("stdout") + // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks + checkReleaseBuilds = false + disable += setOf("GradleOverrides") + } + + packaging { + // Some of the META-INF files conflict with coroutines-test. Exclude them to enable + // our test APK to build (has no effect on our AARs) + resources { + excludes += listOf("/META-INF/AL2.0", "/META-INF/LGPL2.1") + } + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + } + animationsDisabled = true + } + + sourceSets { + named("test") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + named("androidTest") { + java.srcDirs("src/sharedTest/kotlin") + res.srcDirs("src/sharedTest/res") + } + } +} + +metalava { + sourcePaths.setFrom("src/main") + filename.set("api/current.api") + reportLintsAsErrors.set(true) +} + +dependencies { + api(libs.compose.foundation.foundation) + api(libs.snapper) + + implementation(libs.napier) + implementation(libs.kotlin.coroutines.android) + + // ====================== + // Test dependencies + // ====================== + + androidTestImplementation(project(":internal-testutils")) + testImplementation(project(":internal-testutils")) + + androidTestImplementation(project(":testharness")) + testImplementation(project(":testharness")) + + androidTestImplementation(libs.junit) + testImplementation(libs.junit) + + androidTestImplementation(libs.truth) + testImplementation(libs.truth) + + androidTestImplementation(libs.compose.ui.test.junit4) + testImplementation(libs.compose.ui.test.junit4) + + androidTestImplementation(libs.compose.ui.test.manifest) + testImplementation(libs.compose.ui.test.manifest) + + androidTestImplementation(libs.androidx.test.runner) + testImplementation(libs.androidx.test.runner) + + testImplementation(libs.robolectric) +} diff --git a/constraintlayout/extensionLibrary/Views/gradle.properties b/constraintlayout/extensionLibrary/Views/gradle.properties new file mode 100644 index 00000000..03a51122 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=accompanist-pager +POM_NAME=Accompanist Pager layouts +POM_PACKAGING=aar \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Graph.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Graph.java new file mode 100644 index 00000000..6499eefb --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Graph.java @@ -0,0 +1,146 @@ +package android.support.constraintLayout.extlib.graph3d; + + +import android.support.constraintLayout.extlib.graph3d.objects.AxisBox; +import android.support.constraintLayout.extlib.graph3d.objects.Surface3D; + +public class Graph { + Scene3D mScene3D = new Scene3D(); + + int mGraphType = 2; + private float mLastTouchX0 = Float.NaN; + private float mLastTouchY0; + private float mLastTrackBallX; + private float mLastTrackBallY; + double mDownScreenWidth; + Surface3D mSurface; + AxisBox mAxisBox; + float range = 20; + float minZ = -10; + float maxZ = 10; + float mZoomFactor = 1; + long nanoTime; + float time = 0; + int graphWidth; + int graphHeight; + ImageSupport image; + + public interface ImageSupport { + void makeImage(int w, int h); + int[] getBacking(); + } + + public Graph(ImageSupport image) { + this.image = image; + mAxisBox = new AxisBox(); + mAxisBox.setRange(-range, range, -range, range, minZ, maxZ); + mScene3D.addPostObject(mAxisBox); + buildSurface(DEFAULT); + resetCamera(); + } + + public static Surface3D DEFAULT = new Surface3D((x, y, t) -> { + double d = Math.sqrt(x * x + y * y); + return 0.3f * (float) (Math.cos(d) * (y * y - x * x) / (1 + d)); + }); + + public void buildSurface(Surface3D surface3D) { + mSurface = surface3D; + mSurface.setRange(-range, range, -range, range, minZ, maxZ); + mScene3D.setObject(mSurface); + } + + public void resetCamera() { + mScene3D.resetCamera(); + } + + public void setStartTime() { + nanoTime = System.nanoTime(); + } + + public void tick(long now) { + time += (now - nanoTime) * 1E-9f; + nanoTime = now; + mSurface.calcSurface(time, false); + mScene3D.update(); + } + + public void resize(int width, int height) { + graphHeight = height; + graphWidth = width; + image.makeImage(width, height); + mScene3D.setScreenDim(width, height, image.getBacking(), 0x00AAAAAA); + } + + public void trackDown(float x, float y) { + mDownScreenWidth = mScene3D.getScreenWidth(); + mLastTouchX0 = x; + mLastTouchY0 = y; + mScene3D.trackBallDown(mLastTouchX0, mLastTouchY0); + mLastTrackBallX = mLastTouchX0; + mLastTrackBallY = mLastTouchY0; + } + + public void trackDrag(float x, float y) { + if (Float.isNaN(mLastTouchX0)) { + return; + } + float tx = x; + float ty = y; + float moveX = (mLastTrackBallX - tx); + float moveY = (mLastTrackBallY - ty); + if (moveX * moveX + moveY * moveY < 4000f) { + mScene3D.trackBallMove(tx, ty); + } + mLastTrackBallX = tx; + mLastTrackBallY = ty; + } + + public void trackDone() { + mLastTouchX0 = Float.NaN; + mLastTouchY0 = Float.NaN; + } + + public void wheel(float rotation, boolean control) { + if (control) { + mZoomFactor *= (float) Math.pow(1.01, rotation); + mScene3D.setZoom(mZoomFactor); + mScene3D.setUpMatrix(graphWidth, graphHeight); + mScene3D.update(); + } else { + range = range * (float) Math.pow(1.01, rotation); + mSurface.setArraySize(Math.min(300, (int) (range * 5))); + mSurface.setRange(-range, range, -range, range, minZ, maxZ); + mAxisBox.setRange(-range, range, -range, range, minZ, maxZ); + mScene3D.update(); + } + } + + public static final Surface3D BLACK_HOLE_MERGE = new Surface3D((x, y, t) -> { + float d = (float) Math.sqrt(x * x + y * y); + float d2 = (float) Math.pow(x * x + y * y, 0.125); + float angle = (float) Math.atan2(y, x); + float s = (float) Math.sin(d + angle - t * 5); + float s2 = (float) Math.sin(t); + float c = (float) Math.cos(d + angle - t * 5); + return (s2 * s2 + 0.1f) * d2 * 5 * (s + c) / (1 + d * d / 20); + // return (float) (s*s+0.1) * (float) (Math.cos(d-time*5) *(y*y-x*x) /(1+d*d)); + }); + + public void render() { + if (mScene3D.notSetUp()) { + mScene3D.setUpMatrix(graphWidth, graphHeight); + } + + mScene3D.render(mGraphType); + } + + public void lightMovesWithCamera(boolean doesMove) { + mScene3D.mLightMovesWithCamera = doesMove; + } + + public void setSaturation(float sat) { + mSurface.mSaturation = sat; + mScene3D.update(); + } +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Matrix.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Matrix.java new file mode 100644 index 00000000..11684167 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Matrix.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2023 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 android.support.constraintLayout.extlib.graph3d; + +import java.text.DecimalFormat; +import java.util.Arrays; + +/** + * Matrix math class. (For the purposes of this application it is more efficient as has no JNI) + */ +public class Matrix { + + public double []m; + + public void makeRotation(){ + { + double []v = {m[0],m[4],m[8]}; + VectorUtil.normalize(v); + m[0] = v[0]; + m[4] = v[1]; + m[8] = v[2]; + } + { + double []v = {m[1],m[5],m[9]}; + VectorUtil.normalize(v); + m[1] = v[0]; + m[5] = v[1]; + m[9] = v[2]; + } + { + double []v = {m[2],m[6],m[10]}; + VectorUtil.normalize(v); + m[2] = v[0]; + m[6] = v[1]; + m[10] = v[2]; + } + + } + + private static String trim(String s){ + return s.substring(s.length()-7); + } + public void print(){ + DecimalFormat df =new DecimalFormat(" ##0.000"); + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + System.out.print(((j==0)?"[ ":" , ")+trim(df.format(m[i*4+j]))); + } + System.out.println("]"); + } + } + + public Matrix() { + m = new double[4*4]; + setToUnit(); + } + public Matrix(Matrix matrix){ + this(Arrays.copyOf(matrix.m, matrix.m.length)); + } + protected Matrix(double []m){ + this.m = m; + } + + public void setToUnit(){ + for (int i = 1; i < m.length; i++) { + m[i] = 0; + } + m[0]=1; + m[5]=1; + m[10]=1; + m[15]=1; + } + + public void mult4(float[]src,float [] dest){ + for (int i = 0; i < 4; i++) { + int col = i*4; + double sum = 0; + for (int j = 0; j < 4; j++) { + sum += m[col+j]*src[j]; + } + dest[i] = (float) sum; + } + } + + public void mult3(float[]src,float [] dest){ + for (int i = 0; i < 3; i++) { + int col = i*4; + double sum = m[col+3]; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j]; + } + dest[i] =(float) sum; + } + } + public void mult3v(float[]src,float [] dest){ + for (int i = 0; i < 3; i++) { + int col = i*4; + double sum = 0; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j]; + } + dest[i] =(float) sum; + } + } + public void mult3v(float[]src,int off,float [] dest){ + for (int i = 0; i < 3; i++) { + int col = i*4; + double sum = 0; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[off+j]; + } + dest[i] =(float) sum; + } + } + + public void mult4(double[]src,double [] dest){ + for (int i = 0; i < 4; i++) { + int col = i*4; + double sum = 0; + for (int j = 0; j < 4; j++) { + sum += m[col+j]*src[j]; + } + dest[i] = (float) sum; + } + } + + public void mult3(double[]src,double [] dest){ + for (int i = 0; i < 3; i++) { + int col = i*4; + double sum = m[col+3]; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j]; + } + dest[i] =(float) sum; + } + } + public void mult3v(double[]src,double [] dest){ + for (int i = 0; i < 3; i++) { + int col = i*4; + double sum = 0; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j]; + } + dest[i] =(float) sum; + } + } + + public double[] vecmult(double[]src){ + double [] ret = new double[3]; + mult3v(src,ret); + return ret; + } + + public void mult3(float[] src, int off1, float[] dest, int off2) { + + int col = 0*4; + double sum = m[col+3]; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j+off1]; + } + float v0 =(float) sum; + + + col = 1*4; + sum = m[col+3]; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j+off1]; + } + + float v1 =(float) sum; + + + + col = 2*4; + sum = m[col+3]; + for (int j = 0; j < 3; j++) { + sum += m[col+j]*src[j+off1]; + } + float v2 =(float) sum; + + dest[off2] = v0; + dest[1+off2] = v1; + dest[2+off2] = v2; + + } + + + public Matrix invers(Matrix ret) + { + double []inv = ret.m; + + inv[0] = m[5] * m[10] * m[15] - + m[5] * m[11] * m[14] - + m[9] * m[6] * m[15] + + m[9] * m[7] * m[14] + + m[13] * m[6] * m[11] - + m[13] * m[7] * m[10]; + + inv[4] = -m[4] * m[10] * m[15] + + m[4] * m[11] * m[14] + + m[8] * m[6] * m[15] - + m[8] * m[7] * m[14] - + m[12] * m[6] * m[11] + + m[12] * m[7] * m[10]; + + inv[8] = m[4] * m[9] * m[15] - + m[4] * m[11] * m[13] - + m[8] * m[5] * m[15] + + m[8] * m[7] * m[13] + + m[12] * m[5] * m[11] - + m[12] * m[7] * m[9]; + + inv[12] = -m[4] * m[9] * m[14] + + m[4] * m[10] * m[13] + + m[8] * m[5] * m[14] - + m[8] * m[6] * m[13] - + m[12] * m[5] * m[10] + + m[12] * m[6] * m[9]; + + inv[1] = -m[1] * m[10] * m[15] + + m[1] * m[11] * m[14] + + m[9] * m[2] * m[15] - + m[9] * m[3] * m[14] - + m[13] * m[2] * m[11] + + m[13] * m[3] * m[10]; + + inv[5] = m[0] * m[10] * m[15] - + m[0] * m[11] * m[14] - + m[8] * m[2] * m[15] + + m[8] * m[3] * m[14] + + m[12] * m[2] * m[11] - + m[12] * m[3] * m[10]; + + inv[9] = -m[0] * m[9] * m[15] + + m[0] * m[11] * m[13] + + m[8] * m[1] * m[15] - + m[8] * m[3] * m[13] - + m[12] * m[1] * m[11] + + m[12] * m[3] * m[9]; + + inv[13] = m[0] * m[9] * m[14] - + m[0] * m[10] * m[13] - + m[8] * m[1] * m[14] + + m[8] * m[2] * m[13] + + m[12] * m[1] * m[10] - + m[12] * m[2] * m[9]; + + inv[2] = m[1] * m[6] * m[15] - + m[1] * m[7] * m[14] - + m[5] * m[2] * m[15] + + m[5] * m[3] * m[14] + + m[13] * m[2] * m[7] - + m[13] * m[3] * m[6]; + + inv[6] = -m[0] * m[6] * m[15] + + m[0] * m[7] * m[14] + + m[4] * m[2] * m[15] - + m[4] * m[3] * m[14] - + m[12] * m[2] * m[7] + + m[12] * m[3] * m[6]; + + inv[10] = m[0] * m[5] * m[15] - + m[0] * m[7] * m[13] - + m[4] * m[1] * m[15] + + m[4] * m[3] * m[13] + + m[12] * m[1] * m[7] - + m[12] * m[3] * m[5]; + + inv[14] = -m[0] * m[5] * m[14] + + m[0] * m[6] * m[13] + + m[4] * m[1] * m[14] - + m[4] * m[2] * m[13] - + m[12] * m[1] * m[6] + + m[12] * m[2] * m[5]; + + inv[3] = -m[1] * m[6] * m[11] + + m[1] * m[7] * m[10] + + m[5] * m[2] * m[11] - + m[5] * m[3] * m[10] - + m[9] * m[2] * m[7] + + m[9] * m[3] * m[6]; + + inv[7] = m[0] * m[6] * m[11] - + m[0] * m[7] * m[10] - + m[4] * m[2] * m[11] + + m[4] * m[3] * m[10] + + m[8] * m[2] * m[7] - + m[8] * m[3] * m[6]; + + inv[11] = -m[0] * m[5] * m[11] + + m[0] * m[7] * m[9] + + m[4] * m[1] * m[11] - + m[4] * m[3] * m[9] - + m[8] * m[1] * m[7] + + m[8] * m[3] * m[5]; + + inv[15] = m[0] * m[5] * m[10] - + m[0] * m[6] * m[9] - + m[4] * m[1] * m[10] + + m[4] * m[2] * m[9] + + m[8] * m[1] * m[6] - + m[8] * m[2] * m[5]; + + + double det; + det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; + + if (det == 0) { + return null; + } + + det = 1.0 / det; + + + + for (int i = 0; i < 16; i++) { + inv[i] = inv[i] * det; + } + + + return ret; + } + + Matrix mult(Matrix b){ + return new Matrix(multiply(this.m,b.m)); + } + Matrix premult(Matrix b){ + return new Matrix(multiply(b.m,this.m)); + } + + private static double[] multiply(double a[], double b[]) { + double[] resultant = new double[16]; + for(int i = 0; i < 4; i++) { + for(int j = 0; j < 4; j++) { + for(int k = 0; k < 4; k++) { + resultant[i+4*j] += a[i+4*k] * b[k+4*j]; + } + } + } + + return resultant; + } + public static void main(String[] args) { + Matrix m = new Matrix(); + Matrix inv = new Matrix(); + m.m[0] = 100; + m.m[5] = 12; + m.m[10] = 63; + m.m[3] = 12; + m.m[7] = 34; + m.m[11] = 17; + System.out.println(" matrix "); + m.print(); + System.out.println(" inv "); + m.invers(inv).print(); + System.out.println(" inv*matrix "); + + m.mult(m.invers(inv)).print(); + } + +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Object3D.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Object3D.java new file mode 100644 index 00000000..90432385 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Object3D.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2023 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 android.support.constraintLayout.extlib.graph3d; + +/** + * This represents 3d Object in this system. + */ +public class Object3D { + protected float[] vert; + protected float[] normal; + + protected short[] index; + + protected float[] tVert; // the vertices transformed into screen space + protected float mMinX, mMaxX, mMinY, mMaxY, mMinZ, mMaxZ; // bounds in x,y & z + protected int mType = 4; + + float mAmbient = 0.3f; + float mDefuse = 0.7f; + public float mSaturation = 0.6f; + + public int getType() { + return mType; + } + + public void setType(int type) { + this.mType = type; + } + + public void makeVert(int n) { + vert = new float[n * 3]; + tVert = new float[n * 3]; + normal = new float[n * 3]; + } + + public void makeIndexes(int n) { + index = new short[n * 3]; + } + + public void transform(Matrix m) { + for (int i = 0; i < vert.length; i += 3) { + m.mult3(vert, i, tVert, i); + } + } + + public void render(Scene3D s, float[] zbuff, int[] img, int width, int height) { + switch (mType) { + case 0: + raster_height(s, zbuff, img, width, height); + break; + case 1: + raster_outline(s, zbuff, img, width, height); + break; + case 2: + raster_color(s, zbuff, img, width, height); + break; + case 3: + raster_lines(s, zbuff, img, width, height); + break; + case 4: + raster_phong(s, zbuff, img, width, height); + break; + + } + } + + + private void raster_lines(Scene3D s, float[] zbuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + + float height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3; + int val = (int) (255 * Math.abs(height)); + Scene3D.triangle(zbuff, img, 0x10001 * val + 0x100 * (255 - val), w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2]); + + Scene3D.drawline(zbuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f); + Scene3D.drawline(zbuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f); + } + } + + void raster_height(Scene3D s, float[] zbuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + float height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3; + height = (height - mMinZ) / (mMaxZ - mMinZ); + int col = Scene3D.hsvToRgb(height, Math.abs(2 * (height - 0.5f)), (float) Math.sqrt(height)); + Scene3D.triangle(zbuff, img, col, w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2]); + } + } + + // float mSpec = 0.2f; + void raster_color(Scene3D s, float[] zbuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + + VectorUtil.triangleNormal(tVert, p1, p2, p3, s.tmpVec); + float defuse = VectorUtil.dot(s.tmpVec, s.mTransformedLight); + + float height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3; + height = (height - mMinZ) / (mMaxZ - mMinZ); + float bright = Math.min(1, Math.max(0, mDefuse * defuse + mAmbient)); + float hue = (float) (height - Math.floor(height)); + float sat = 0.8f; + int col = Scene3D.hsvToRgb(hue, sat, bright); + Scene3D.triangle(zbuff, img, col, w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2]); + } + } + + private int color(float hue, float sat, float bright) { + hue = hue(hue); + bright = bright(bright); + return Scene3D.hsvToRgb(hue, sat, bright); + } + + private float hue(float hue) { + return (float) (hue - Math.floor(hue)); + } + + private float bright(float bright) { + return Math.min(1, Math.max(0, bright)); + } + + private float defuse(float[] normals, int off, float[] light) { + // s.mMatrix.mult3v(normal,off,s.tmpVec); + return Math.abs(VectorUtil.dot(normal, off, light)); + } + + void raster_phong(Scene3D s, float[] zbuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + + // VectorUtil.triangleNormal(tVert, p1, p2, p3, s.tmpVec); + + +// float defuse1 = VectorUtil.dot(normal, p1, s.mTransformedLight); +// float defuse2 = VectorUtil.dot(normal, p2, s.mTransformedLight); +// float defuse3 = VectorUtil.dot(normal, p3, s.mTransformedLight); + float defuse1 = defuse(normal, p1, s.mTransformedLight); + float defuse2 = defuse(normal, p2, s.mTransformedLight); + float defuse3 = defuse(normal, p3, s.mTransformedLight); + float col1_hue = hue((vert[p1 + 2] - mMinZ) / (mMaxZ - mMinZ)); + float col2_hue = hue((vert[p2 + 2] - mMinZ) / (mMaxZ - mMinZ)); + float col3_hue = hue((vert[p3 + 2] - mMinZ) / (mMaxZ - mMinZ)); + float col1_bright = bright(mDefuse * defuse1 + mAmbient); + float col2_bright = bright(mDefuse * defuse2 + mAmbient); + float col3_bright = bright(mDefuse * defuse3 + mAmbient); + + Scene3D.trianglePhong(zbuff, img, + col1_hue, col1_bright, + col2_hue, col2_bright, + col3_hue, col3_bright, + mSaturation, + w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2]); + } + } + + void raster_outline(Scene3D s, float[] zBuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + + Scene3D.triangle(zBuff, img, s.background, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2]); + + Scene3D.drawline(zBuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2]); + + Scene3D.drawline(zBuff, img, s.lineColor, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2]); + } + } + + public double[] center() { + double[] look_point = { + (mMinX + mMaxX) / 2, (mMinY + mMaxY) / 2, (mMinZ + mMaxZ) / 2 + }; + return look_point; + } + + public float centerX() { + return (mMaxX + mMinX) / 2; + } + + public float centerY() { + return (mMaxY + mMinY) / 2; + } + + public float rangeX() { + return (mMaxX - mMinX) / 2; + } + + public float rangeY() { + return (mMaxY - mMinY) / 2; + } + + public double size() { + + return Math.hypot((mMaxX - mMinX), Math.hypot((mMaxY - mMinY), (mMaxZ - mMinZ))) / 2; + } + +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Quaternion.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Quaternion.java new file mode 100644 index 00000000..15f71a72 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Quaternion.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2020 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 android.support.constraintLayout.extlib.graph3d; + +/** + * This is a class that represents a Quaternion + * Used to implement a virtual trackball with no "gimbal lock" + * see https://en.wikipedia.org/wiki/Quaternion + */ +public class Quaternion { + private final double []x=new double[4]; // w,x,y,z, + + public void set(double w,double x,double y,double z){ + this.x[0] = w; + this.x[1] = x; + this.x[2] = y; + this.x[3] = z; + } + + private static double[] cross(double[]a,double []b){ + double out0= a[1] * b[2] - b[1] * a[2]; + double out1= a[2] * b[0] - b[2] * a[0]; + double out2= a[0] * b[1] - b[0] * a[1]; + return new double[]{out0,out1,out2}; + } + private static double dot(double[]a,double []b){ + return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]; + } + + private static double[] normal(double []a){ + double norm = Math.sqrt(dot(a,a)); + return new double[]{a[0]/norm,a[1]/norm,a[2]/norm}; + } + + public void set(double []v1,double []v2){ + double []vec1 = normal(v1); + double []vec2 = normal(v2); + double []axis = normal(cross(vec1,vec2)); + double angle = Math.acos(dot(vec1,vec2)); + set(angle,axis); + } + + public static double calcAngle(double []v1,double []v2){ + double []vec1 = normal(v1); + double []vec2 = normal(v2); + return Math.acos(dot(vec1,vec2)); + } + + public static double [] calcAxis(double []v1,double []v2){ + double []vec1 = normal(v1); + double []vec2 = normal(v2); + return normal(cross(vec1,vec2)); + } + + public void set(double angle, double []axis){ + x[0]=Math.cos(angle/2); + double sin=Math.sin(angle/2); + x[1]=axis[0]*sin; + x[2]=axis[1]*sin; + x[3]=axis[2]*sin; + } + + public Quaternion(double x0, double x1, double x2, double x3) { + x[0] = x0; + x[1] = x1; + x[2] = x2; + x[3] = x3; + } + + public Quaternion conjugate() { + return new Quaternion(x[0], -x[1], -x[2], -x[3]); + } + + public Quaternion plus(Quaternion b) { + Quaternion a = this; + return new Quaternion(a.x[0]+b.x[0], a.x[1]+b.x[1], a.x[2]+b.x[2], a.x[3]+b.x[3]); + } + + + public Quaternion times(Quaternion b) { + Quaternion a = this; + double y0 = a.x[0]*b.x[0] - a.x[1]*b.x[1] - a.x[2]*b.x[2] - a.x[3]*b.x[3]; + double y1 = a.x[0]*b.x[1] + a.x[1]*b.x[0] + a.x[2]*b.x[3] - a.x[3]*b.x[2]; + double y2 = a.x[0]*b.x[2] - a.x[1]*b.x[3] + a.x[2]*b.x[0] + a.x[3]*b.x[1]; + double y3 = a.x[0]*b.x[3] + a.x[1]*b.x[2] - a.x[2]*b.x[1] + a.x[3]*b.x[0]; + return new Quaternion(y0, y1, y2, y3); + } + + public Quaternion inverse() { + double d = x[0]*x[0] + x[1]*x[1] + x[2]*x[2] + x[3]*x[3]; + return new Quaternion(x[0]/d, -x[1]/d, -x[2]/d, -x[3]/d); + } + + public Quaternion divides(Quaternion b) { + Quaternion a = this; + return a.inverse().times(b); + } + + + public double[] rotateVec(double []v){ + + double v0 = v[0]; + double v1 = v[1]; + double v2 = v[2]; + + double s = x[1] * v0 + x[2] * v1 + x[3] * v2; + + double n0 = 2 * (x[0] * (v0 * x[0] - (x[2] * v2 - x[3] * v1)) + s * x[1]) - v0; + double n1 = 2 * (x[0] * (v1 * x[0] - (x[3] * v0 - x[1] * v2)) + s * x[2]) - v1; + double n2 = 2 * (x[0] * (v2 * x[0] - (x[1] * v1 - x[2] * v0)) + s * x[3]) - v2; + + return new double[]{ n0,n1,n2}; + + } + + void matrix(){ + double xx = x[1] * x[1]; + double xy = x[1] * x[2]; + double xz = x[1] * x[3]; + double xw = x[1] * x[0]; + + double yy = x[2] * x[2]; + double yz = x[2] * x[3]; + double yw = x[2] * x[0]; + + double zz = x[3] * x[3]; + double zw = x[3] * x[0]; + double []m = new double[16]; + m[0] = 1 - 2 * ( yy + zz ); + m[1] = 2 * ( xy - zw ); + m[2] = 2 * ( xz + yw ); + + m[4] = 2 * ( xy + zw ); + m[5] = 1 - 2 * ( xx + zz ); + m[6] = 2 * ( yz - xw ); + + m[8] = 2 * ( xz - yw ); + m[9] = 2 * ( yz + xw ); + m[10] = 1 - 2 * ( xx + yy ); + + m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0; + m[15] = 1; + } +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Scene3D.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Scene3D.java new file mode 100644 index 00000000..a1366792 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/Scene3D.java @@ -0,0 +1,602 @@ +/* + * Copyright (C) 2020 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 android.support.constraintLayout.extlib.graph3d; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * This renders 3Dimensional Objects. + */ +public class Scene3D { + private static final String TAG = "SurfaceGen"; + ViewMatrix mMatrix = new ViewMatrix(); + Matrix mInverse = new Matrix(); + Object3D mObject3D; + ArrayList mPreObjects = new ArrayList(); + ArrayList mPostObjects = new ArrayList(); + float[] zBuff; + int[] img; + + private float[] light = {0, 0, 1}; // The direction of the light source + public float[] mTransformedLight = {0, 1, 1}; // The direction of the light source + public boolean mLightMovesWithCamera = false; + + int width, height; + public float[] tmpVec = new float[3]; + int lineColor = 0xFF000000; + private final float epslonX = 0.000005232f; + private final float epslonY = 0.00000898f; + private Function mFunction; + private float mZoomZ = 1; + int background; + + public int getLineColor() { + return lineColor; + } + + public void setLineColor(int lineColor) { + this.lineColor = lineColor; + } + + class Box { + float[][] m_box = {{1, 1, 1}, {2, 3, 2}}; + int[] m_x1 = {0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}; + int[] m_y1 = {0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1}; + int[] m_z1 = {0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1}; + int[] m_x2 = {0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1}; + int[] m_y2 = {0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1}; + int[] m_z2 = {1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}; + float[] m_point1 = new float[3]; + float[] m_point2 = new float[3]; + float[] m_draw1 = new float[3]; + float[] m_draw2 = new float[3]; + + void drawLines(LineRender r) { + for (int i = 0; i < 12; i++) { + m_point1[0] = m_box[m_x1[i]][0]; + m_point1[1] = m_box[m_y1[i]][1]; + m_point1[2] = m_box[m_z1[i]][2]; + m_point2[0] = m_box[m_x2[i]][0]; + m_point2[1] = m_box[m_y2[i]][1]; + m_point2[2] = m_box[m_z2[i]][2]; + + mInverse.mult3(m_point1, m_draw1); + mInverse.mult3(m_point2, m_draw2); + + r.draw((int) m_draw1[0], (int) m_draw1[1], (int) m_draw2[0], (int) m_draw2[1]); + } + } + } + + + { + VectorUtil.normalize(light); + } + + public void transformTriangles() { + transform(); + } + + public void transform() { + Matrix m = mInverse; + if (mLightMovesWithCamera) { + mMatrix.mult3v(light, mTransformedLight); + VectorUtil.normalize(mTransformedLight); + } else { + System.arraycopy(light, 0, mTransformedLight, 0, 3); + } + + mObject3D.transform(m); + for (Object3D obj : mPreObjects) { + obj.transform(m); + } + for (Object3D obj : mPostObjects) { + obj.transform(m); + } + } + + public double getScreenWidth() { + return mMatrix.getScreenWidth(); + } + + public void setScreenWidth(double sw) { + mMatrix.setScreenWidth(sw); + mMatrix.calcMatrix(); + mMatrix.invers(mInverse); + transform(); + } + + public void trackBallDown(float x, float y) { + mMatrix.trackBallDown(x, y); + mMatrix.invers(mInverse); + } + + public void trackBallMove(float x, float y) { + mMatrix.trackBallMove(x, y); + mMatrix.invers(mInverse); + transform(); + } + + public void trackBallUP(float x, float y) { + mMatrix.trackBallUP(x, y); + mMatrix.invers(mInverse); + } + + public void update() { + mMatrix.invers(mInverse); + transform(); + } + + public void panDown(float x, float y) { + mMatrix.panDown(x, y); + mMatrix.invers(mInverse); + } + + public void panMove(float x, float y) { + mMatrix.panMove(x, y); + mMatrix.invers(mInverse); + transform(); + } + + public void panUP() { + mMatrix.panUP(); + } + + public String getLookPoint() { + return Arrays.toString(mMatrix.getLookPoint()); + } + + public void setScreenDim(int width, int height, int[] img, int background) { + mMatrix.setScreenDim(width, height); + setupBuffers(width, height, img, background); + setUpMatrix(width, height); + transform(); + } + + public float getZoom() { + return mZoomZ; + } + + public void setZoom(float zoom) { + this.mZoomZ = zoom; + } + + public void setUpMatrix(int width, int height) { + setUpMatrix(width, height, false); + } + + public void setUpMatrix(int width, int height, boolean resetOrientation) { + double[] look_point = mObject3D.center(); + + double diagonal = mObject3D.size() * mZoomZ; + + mMatrix.setLookPoint(look_point); + if (resetOrientation) { + double[] eye_point = {look_point[0] - diagonal, look_point[1] - diagonal, look_point[2] + diagonal}; + mMatrix.setEyePoint(eye_point); + double[] up_vector = {0, 0, 1}; + mMatrix.setUpVector(up_vector); + } else { + mMatrix.fixUpPoint(); + } + double screenWidth = diagonal * 2; + mMatrix.setScreenWidth(screenWidth); + mMatrix.setScreenDim(width, height); + mMatrix.calcMatrix(); + mMatrix.invers(mInverse); + } + + public boolean notSetUp() { + return mInverse == null; + } + + interface LineRender { + void draw(int x1, int y1, int x2, int y2); + } + + public void drawBox(LineRender g) { + } + + public interface Function { + float eval(float x, float y); + } + + + public void addPreObject(Object3D obj) { + mPreObjects.add(obj); + } + + public void setObject(Object3D obj) { + mObject3D = obj; + } + + public void addPostObject(Object3D obj) { + mPostObjects.add(obj); + } + + public void resetCamera() { + setUpMatrix(width, height, true); + transform(); + } + + private final static int min(int x1, int x2, int x3) { + return (x1 > x2) ? ((x2 > x3) ? x3 : x2) : ((x1 > x3) ? x3 : x1); + } + + private final static int max(int x1, int x2, int x3) { + return (x1 < x2) ? ((x2 < x3) ? x3 : x2) : ((x1 < x3) ? x3 : x1); + } + + public void setupBuffers(int w, int h, int[] img, int background) { + width = w; + height = h; + this.background = background; + zBuff = new float[w * h]; + this.img = img; + Arrays.fill(zBuff, Float.MAX_VALUE); + Arrays.fill(img, background); + } + + void render(int type) { + if (zBuff == null) { + return; + } + Arrays.fill(zBuff, Float.MAX_VALUE); + Arrays.fill(img, background); + + for (Object3D mPreObject : mPreObjects) { + mPreObject.render(this, zBuff, img, width, height); + } + mObject3D.render(this, zBuff, img, width, height); + for (Object3D mPreObject : mPostObjects) { + mPreObject.render(this, zBuff, img, width, height); + } + } + + public static int hsvToRgb(float hue, float saturation, float value) { + int h = (int) (hue * 6); + float f = hue * 6 - h; + int p = (int) (0.5f + 255 * value * (1 - saturation)); + int q = (int) (0.5f + 255 * value * (1 - f * saturation)); + int t = (int) (0.5f + 255 * value * (1 - (1 - f) * saturation)); + int v = (int) (0.5f + 255 * value); + switch (h) { + case 0: + return 0XFF000000 | (v << 16) + (t << 8) + p; + case 1: + return 0XFF000000 | (q << 16) + (v << 8) + p; + case 2: + return 0XFF000000 | (p << 16) + (v << 8) + t; + case 3: + return 0XFF000000 | (p << 16) + (q << 8) + v; + case 4: + return 0XFF000000 | (t << 16) + (p << 8) + v; + case 5: + return 0XFF000000 | (v << 16) + (p << 8) + q; + + } + return 0; + } + + + public static void drawline(float[] zbuff, int[] img, int color, int w, int h, + float fx1, float fy1, float fz1, + float fx2, float fy2, float fz2 + ) { + float dx = fx2 - fx1, dy = fy2 - fy1, dz = fz2 - fz1; + float steps = (float) Math.hypot(dx, Math.hypot(dy, dz)); + + for (float t = 0; t < 1; t += 1 / steps) { + float px = fx1 + t * dx; + float py = fy1 + t * dy; + float pz = fz1 + t * dz; + int ipx = (int) px; + int ipy = (int) py; + if (ipx < 0 || ipx >= w || ipy < 0 || ipy >= h) { + continue; + } + + int point = ipx + w * ipy; + if (zbuff[point] >= pz - 2) { + img[point] = color; + } + + } + } + + public static boolean isBackface( + float fx3, float fy3, float fz3, + float fx2, float fy2, float fz2, + float fx1, float fy1, float fz1) { + + return (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0); + + } + + + public static void triangle(float[] zbuff, int[] img, int color, int w, int h, + float fx3, float fy3, float fz3, + float fx2, float fy2, float fz2, + float fx1, float fy1, float fz1) { + + if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) { + float tmpx = fx1; + float tmpy = fy1; + float tmpz = fz1; + fx1 = fx2; + fy1 = fy2; + fz1 = fz2; + fx2 = tmpx; + fy2 = tmpy; + fz2 = tmpz; + } + // using maxmima + // string(solve([x1*dx+y1*dy+zoff=z1,x2*dx+y2*dy+zoff=z2,x3*dx+y3*dy+zoff=z3],[dx,dy,zoff])); + double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) + * fy1); + + if (d == 0) { + return; + } + float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3) + * fz1) / d); + float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3) + * fz1) / d); + float zoff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1 + * (fx2 * fz3 - fx3 * fz2) + (fx3 * fy2 - fx2 * fy3) * fz1) / d); + + // 28.4 fixed-point coordinates + + int Y1 = (int) (16.0f * fy1 + .5f); + int Y2 = (int) (16.0f * fy2 + .5f); + int Y3 = (int) (16.0f * fy3 + .5f); + + int X1 = (int) (16.0f * fx1 + .5f); + int X2 = (int) (16.0f * fx2 + .5f); + int X3 = (int) (16.0f * fx3 + .5f); + + int DX12 = X1 - X2; + int DX23 = X2 - X3; + int DX31 = X3 - X1; + + int DY12 = Y1 - Y2; + int DY23 = Y2 - Y3; + int DY31 = Y3 - Y1; + + int FDX12 = DX12 << 4; + int FDX23 = DX23 << 4; + int FDX31 = DX31 << 4; + + int FDY12 = DY12 << 4; + int FDY23 = DY23 << 4; + int FDY31 = DY31 << 4; + + int minx = (min(X1, X2, X3) + 0xF) >> 4; + int maxx = (max(X1, X2, X3) + 0xF) >> 4; + int miny = (min(Y1, Y2, Y3) + 0xF) >> 4; + int maxy = (max(Y1, Y2, Y3) + 0xF) >> 4; + + if (miny < 0) { + miny = 0; + } + if (minx < 0) { + minx = 0; + } + if (maxx > w) { + maxx = w; + } + if (maxy > h) { + maxy = h; + } + int off = miny * w; + + int C1 = DY12 * X1 - DX12 * Y1; + int C2 = DY23 * X2 - DX23 * Y2; + int C3 = DY31 * X3 - DX31 * Y3; + + if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) { + C1++; + } + if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) { + C2++; + } + if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) { + C3++; + } + int CY1 = C1 + DX12 * (miny << 4) - DY12 * (minx << 4); + int CY2 = C2 + DX23 * (miny << 4) - DY23 * (minx << 4); + int CY3 = C3 + DX31 * (miny << 4) - DY31 * (minx << 4); + + for (int y = miny; y < maxy; y++) { + int CX1 = CY1; + int CX2 = CY2; + int CX3 = CY3; + float p = zoff + dy * y; + for (int x = minx; x < maxx; x++) { + if (CX1 > 0 && CX2 > 0 && CX3 > 0) { + int point = x + off; + float zval = p + dx * x; + if (zbuff[point] > zval) { + zbuff[point] = zval; + img[point] = color; + } + } + CX1 -= FDY12; + CX2 -= FDY23; + CX3 -= FDY31; + } + CY1 += FDX12; + CY2 += FDX23; + CY3 += FDX31; + off += w; + } + } + + public static void trianglePhong(float[] zbuff, int[] img, + float h3, float b3, + float h2, float b2, + float h1, float b1, + float sat, + int w, int h, + float fx3, float fy3, float fz3, + float fx2, float fy2, float fz2, + float fx1, float fy1, float fz1) { + + if (((fx1 - fx2) * (fy3 - fy2) - (fy1 - fy2) * (fx3 - fx2)) < 0) { + float tmpx = fx1; + float tmpy = fy1; + float tmpz = fz1; + fx1 = fx2; + fy1 = fy2; + fz1 = fz2; + fx2 = tmpx; + fy2 = tmpy; + fz2 = tmpz; + float tmph = h1; + float tmpb = b1; + + h1 = h2; + b1 = b2; + + h2 = tmph; + b2 = tmpb; + + } + // using maxmima + // string(solve([x1*dx+y1*dy+zoff=z1,x2*dx+y2*dy+zoff=z2,x3*dx+y3*dy+zoff=z3],[dx,dy,zoff])); + double d = (fx1 * (fy3 - fy2) - fx2 * fy3 + fx3 * fy2 + (fx2 - fx3) + * fy1); + + if (d == 0) { + return; + } + float dx = (float) (-(fy1 * (fz3 - fz2) - fy2 * fz3 + fy3 * fz2 + (fy2 - fy3) + * fz1) / d); + float dy = (float) ((fx1 * (fz3 - fz2) - fx2 * fz3 + fx3 * fz2 + (fx2 - fx3) + * fz1) / d); + float zoff = (float) ((fx1 * (fy3 * fz2 - fy2 * fz3) + fy1 + * (fx2 * fz3 - fx3 * fz2) + (fx3 * fy2 - fx2 * fy3) * fz1) / d); + + float dhx = (float) (-(fy1 * (h3 - h2) - fy2 * h3 + fy3 * h2 + (fy2 - fy3) + * h1) / d); + float dhy = (float) ((fx1 * (h3 - h2) - fx2 * h3 + fx3 * h2 + (fx2 - fx3) + * h1) / d); + float hoff = (float) ((fx1 * (fy3 * h2 - fy2 * h3) + fy1 + * (fx2 * h3 - fx3 * h2) + (fx3 * fy2 - fx2 * fy3) * h1) / d); + + + float dbx = (float) (-(fy1 * (b3 - b2) - fy2 * b3 + fy3 * b2 + (fy2 - fy3) + * b1) / d); + float dby = (float) ((fx1 * (b3 - b2) - fx2 * b3 + fx3 * b2 + (fx2 - fx3) + * b1) / d); + float boff = (float) ((fx1 * (fy3 * b2 - fy2 * b3) + fy1 + * (fx2 * b3 - fx3 * b2) + (fx3 * fy2 - fx2 * fy3) * b1) / d); + + // 28.4 fixed-point coordinates + + int Y1 = (int) (16.0f * fy1 + .5f); + int Y2 = (int) (16.0f * fy2 + .5f); + int Y3 = (int) (16.0f * fy3 + .5f); + + int X1 = (int) (16.0f * fx1 + .5f); + int X2 = (int) (16.0f * fx2 + .5f); + int X3 = (int) (16.0f * fx3 + .5f); + + int DX12 = X1 - X2; + int DX23 = X2 - X3; + int DX31 = X3 - X1; + + int DY12 = Y1 - Y2; + int DY23 = Y2 - Y3; + int DY31 = Y3 - Y1; + + int FDX12 = DX12 << 4; + int FDX23 = DX23 << 4; + int FDX31 = DX31 << 4; + + int FDY12 = DY12 << 4; + int FDY23 = DY23 << 4; + int FDY31 = DY31 << 4; + + int minx = (min(X1, X2, X3) + 0xF) >> 4; + int maxx = (max(X1, X2, X3) + 0xF) >> 4; + int miny = (min(Y1, Y2, Y3) + 0xF) >> 4; + int maxy = (max(Y1, Y2, Y3) + 0xF) >> 4; + + if (miny < 0) { + miny = 0; + } + if (minx < 0) { + minx = 0; + } + if (maxx > w) { + maxx = w; + } + if (maxy > h) { + maxy = h; + } + int off = miny * w; + + int C1 = DY12 * X1 - DX12 * Y1; + int C2 = DY23 * X2 - DX23 * Y2; + int C3 = DY31 * X3 - DX31 * Y3; + + if (DY12 < 0 || (DY12 == 0 && DX12 > 0)) { + C1++; + } + if (DY23 < 0 || (DY23 == 0 && DX23 > 0)) { + C2++; + } + if (DY31 < 0 || (DY31 == 0 && DX31 > 0)) { + C3++; + } + int CY1 = C1 + DX12 * (miny << 4) - DY12 * (minx << 4); + int CY2 = C2 + DX23 * (miny << 4) - DY23 * (minx << 4); + int CY3 = C3 + DX31 * (miny << 4) - DY31 * (minx << 4); + + for (int y = miny; y < maxy; y++) { + int CX1 = CY1; + int CX2 = CY2; + int CX3 = CY3; + float p = zoff + dy * y; + float ph = hoff + dhy * y; + float pb = boff + dby * y; + for (int x = minx; x < maxx; x++) { + if (CX1 > 0 && CX2 > 0 && CX3 > 0) { + int point = x + off; + float zval = p + dx * x; + float hue = ph + dhx * x; + float bright = pb + dbx * x; + if (zbuff[point] > zval) { + zbuff[point] = zval; + img[point] = Scene3D.hsvToRgb(hue, sat, bright);; + } + } + CX1 -= FDY12; + CX2 -= FDY23; + CX3 -= FDY31; + } + CY1 += FDX12; + CY2 += FDX23; + CY3 += FDX31; + off += w; + } + } + + +} \ No newline at end of file diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/VectorUtil.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/VectorUtil.java new file mode 100644 index 00000000..5603ec63 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/VectorUtil.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2020 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 android.support.constraintLayout.extlib.graph3d; + +import java.text.DecimalFormat; + +/** + * A few utilities for vector calculations. + */ +public class VectorUtil { + public static void sub(double[] a, double[] b, double[] out) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + } + + public static void mult(double[] a, double b, double[] out) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + } + + public static double dot(double[] a, double[] b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + + public static double norm(double[] a) { + return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); + } + + public static double norm(float[] a) { + return Math.sqrt(a[0] * a[0] + a[1] * a[1] + a[2] * a[2]); + } + + public static void cross(double[] a, double[] b, double[] out) { + double out0 = a[1] * b[2] - b[1] * a[2]; + double out1 = a[2] * b[0] - b[2] * a[0]; + double out2 = a[0] * b[1] - b[0] * a[1]; + out[0] = out0; + out[1] = out1; + out[2] = out2; + } + + public static void normalize(double[] a) { + double norm = norm(a); + a[0] /= norm; + a[1] /= norm; + a[2] /= norm; + } + public static void normalize(float[] a) { + float norm = (float) norm(a); + a[0] /= norm; + a[1] /= norm; + a[2] /= norm; + } + public static void add(double[] a, double[] b, + double[] out) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + } + public static void madd(double[] a, double x, double[] b, + double[] out) { + out[0] = x * a[0] + b[0]; + out[1] = x * a[1] + b[1]; + out[2] = x * a[2] + b[2]; + } + public static void triangleNormal(float[] vert, int p1, int p2, int p3, float[] norm) { + float x1 = vert[p2] - vert[p1]; + float y1 = vert[p2 + 1] - vert[p1 + 1]; + float z1 = vert[p2 + 2] - vert[p1 + 2]; + float x2 = vert[p3] - vert[p1]; + float y2 = vert[p3 + 1] - vert[p1 + 1]; + float z2 = vert[p3 + 2] - vert[p1 + 2]; + + cross(x1, y1, z1, x2, y2, z2, norm); + float n = (float) norm(norm); + norm[0] /= n; + norm[1] /= n; + norm[2] /= n; + } + public static float dot(float[] a, float[] b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; + } + public static float dot(float[] a,int offset, float[] b) { + return a[offset] * b[0] + a[1+offset] * b[1] + a[2+offset] * b[2]; + } + public static void cross(float a0, float a1, float a2, float b0, float b1, float b2, float[] out) { + float out0 = a1 * b2 - b1 * a2; + float out1 = a2 * b0 - b2 * a0; + float out2 = a0 * b1 - b0 * a1; + out[0] = out0; + out[1] = out1; + out[2] = out2; + } + + private static String trim(String s){ + return s.substring(s.length()-7); + } + + public static String vecToString(float[] light) { + DecimalFormat df =new DecimalFormat(" ##0.000"); + String str = "["; + for (int i = 0; i < 3; i++) { + if (Float.isNaN(light[i])) { + str+=(((i == 0) ? "" : " , ") + trim(" NAN")); + continue; + } + str+=(((i == 0) ? "" : " , ") + trim(df.format(light[i]))); + } + return str+ "]"; + + } +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/ViewMatrix.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/ViewMatrix.java new file mode 100644 index 00000000..9e098ed0 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/ViewMatrix.java @@ -0,0 +1,380 @@ +/* + * Copyright (C) 2020 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 com.google.constraintlayout.ext.graph3d; + +import java.text.DecimalFormat; +import java.util.Arrays; + +/** + * This calculates the matrix that transforms triangles from world space to screen space. + */ +public class ViewMatrix extends Matrix { + double[] mLookPoint; + double[] mEyePoint; + double[] mUpVector; + double mScreenWidth; + int[] mScreenDim; + double[] mTmp1 = new double[3]; + public final static char UP_AT = 0x001; + public final static char DOWN_AT = 0x002; + public final static char RIGHT_AT = 0x010; + public final static char LEFT_AT = 0x020; + public final static char FORWARD_AT = 0x100; + public final static char BEHIND_AT = 0x200; + + private static String toStr(double d) { + String s = " " + df.format(d); + return s.substring(s.length() - 8); + } + + private static String toStr(double[] d) { + String s = "["; + for (int i = 0; i < d.length; i++) { + s += toStr(d[i]); + } + + return s + "]"; + } + + private static DecimalFormat df = new DecimalFormat("##0.000"); + + @Override + public void print() { + System.out.println("mLookPoint :" + toStr(mLookPoint)); + System.out.println("mEyePoint :" + toStr(mEyePoint)); + System.out.println("mUpVector :" + toStr(mUpVector)); + System.out.println("mScreenWidth:" + toStr(mScreenWidth)); + System.out.println("mScreenDim :[" + mScreenDim[0] + "," + mScreenDim[1] + "]"); + } + + public ViewMatrix() { + + } + + public void setScreenDim(int x, int y) { + mScreenDim = new int[]{x, y}; + } + + public double[] getLookPoint() { + return mLookPoint; + } + + public void setLookPoint(double[] mLookPoint) { + this.mLookPoint = mLookPoint; + } + + /** + * return the distance from the look point to the eye point + * @return + */ + public double calCameraDistance() { + double[] zv = { + mEyePoint[0] - mLookPoint[0], + mEyePoint[1] - mLookPoint[1], + mEyePoint[2] - mLookPoint[2] + }; + return VectorUtil.norm(zv); + } + + public double[] getEyePoint() { + return mEyePoint; + } + + public void setEyePoint(double[] mEyePoint) { + this.mEyePoint = mEyePoint; + } + + public double[] getUpVector() { + return mUpVector; + } + + public void setUpVector(double[] mUpVector) { + this.mUpVector = mUpVector; + } + + public double getScreenWidth() { + return mScreenWidth; + } + + public void setScreenWidth(double screenWidth) { + this.mScreenWidth = screenWidth; + } + + public void makeUnit() { + + } + + public void fixUpPoint() { + double[] zv = { + mEyePoint[0] - mLookPoint[0], + mEyePoint[1] - mLookPoint[1], + mEyePoint[2] - mLookPoint[2] + }; + VectorUtil.normalize(zv); + double[] rv = new double[3]; + VectorUtil.cross(zv, mUpVector, rv); + + VectorUtil.cross(zv, rv, mUpVector); + VectorUtil.normalize(mUpVector); + VectorUtil.mult(mUpVector, -1, mUpVector); + + } + + public void calcMatrix() { + if (mScreenDim == null) { + return; + } + double scale = mScreenWidth / mScreenDim[0]; + double[] zv = { + mLookPoint[0] - mEyePoint[0], + mLookPoint[1] - mEyePoint[1], + mLookPoint[2] - mEyePoint[2] + }; + VectorUtil.normalize(zv); + + + double[] m = new double[16]; + m[2] = zv[0] * scale; + m[6] = zv[1] * scale; + m[10] = zv[2] * scale; + m[14] = 0; + + calcRight(zv, mUpVector, zv); + double[] right = zv; + + m[0] = right[0] * scale; + m[4] = right[1] * scale; + m[8] = right[2] * scale; + m[12] = 0; + + m[1] = -mUpVector[0] * scale; + m[5] = -mUpVector[1] * scale; + m[9] = -mUpVector[2] * scale; + m[13] = 0; + double sw = mScreenDim[0] / 2 - 0.5; + double sh = mScreenDim[1] / 2 - 0.5; + double sz = -0.5; + m[3] = mEyePoint[0] - (m[0] * sw + m[1] * sh + m[2] * sz); + m[7] = mEyePoint[1] - (m[4] * sw + m[5] * sh + m[6] * sz); + m[11] = mEyePoint[2] - (m[8] * sw + m[9] * sh + m[10] * sz); + + m[15] = 1; + this.m = m; + } + + static void calcRight(double[] a, double[] b, double[] out) { + VectorUtil.cross(a, b, out); + } + + public static void main(String[] args) { + double[] up = {0, 0, 1}; + double[] look = {0, 0, 0}; + double[] eye = {-10, 0, 0}; + ViewMatrix v = new ViewMatrix(); + v.setEyePoint(eye); + v.setLookPoint(look); + v.setUpVector(up); + v.setScreenWidth(10); + v.setScreenDim(512, 512); + v.calcMatrix(); + } + + private void calcLook(Object3D tri, float[] voxelDim, int w, int h) { + float minx = Float.MAX_VALUE, miny = Float.MAX_VALUE, minz = Float.MAX_VALUE; + float maxx = -Float.MAX_VALUE, maxy = -Float.MAX_VALUE, maxz = -Float.MAX_VALUE; + + for (int i = 0; i < tri.vert.length; i += 3) { + maxx = Math.max(tri.vert[i], maxx); + minx = Math.min(tri.vert[i], minx); + maxy = Math.max(tri.vert[i + 1], maxy); + miny = Math.min(tri.vert[i + 1], miny); + maxz = Math.max(tri.vert[i + 2], maxz); + minz = Math.min(tri.vert[i + 2], minz); + } + mLookPoint = new double[]{voxelDim[0] * (maxx + minx) / 2, voxelDim[1] * (maxy + miny) / 2, voxelDim[2] * (maxz + minz) / 2}; + + + mScreenWidth = Math.max(voxelDim[0] * (maxx - minx), Math.max(voxelDim[1] * (maxy - miny), voxelDim[2] * (maxz - minz))) * 2; + } + + private void calcLook(Object3D triW, int w, int h) { + float minx = Float.MAX_VALUE, miny = Float.MAX_VALUE, minz = Float.MAX_VALUE; + float maxx = -Float.MAX_VALUE, maxy = -Float.MAX_VALUE, maxz = -Float.MAX_VALUE; + + for (int i = 0; i < triW.vert.length; i += 3) { + maxx = Math.max(triW.vert[i], maxx); + minx = Math.min(triW.vert[i], minx); + maxy = Math.max(triW.vert[i + 1], maxy); + miny = Math.min(triW.vert[i + 1], miny); + maxz = Math.max(triW.vert[i + 2], maxz); + minz = Math.min(triW.vert[i + 2], minz); + } + mLookPoint = new double[]{(maxx + minx) / 2, (maxy + miny) / 2, (maxz + minz) / 2}; + + mScreenWidth = Math.max((maxx - minx), Math.max((maxy - miny), (maxz - minz))); + } + + public void look(char dir, Object3D tri, float[] voxelDim, int w, int h) { + calcLook(tri, w, h); + int dx = ((dir >> 4) & 0xF); + int dy = ((dir >> 8) & 0xF); + int dz = ((dir >> 0) & 0xF); + if (dx > 1) { + dx = -1; + } + if (dy > 1) { + dy = -1; + } + if (dz > 1) { + dz = -1; + } + mEyePoint = new double[]{mLookPoint[0] + 2 * mScreenWidth * dx, + mLookPoint[1] + 2 * mScreenWidth * dy, + mLookPoint[2] + 2 * mScreenWidth * dz}; + double[] zv = new double[]{-dx, -dy, -dz}; + double[] rv = new double[]{(dx == 0) ? 1 : 0, (dx == 0) ? 0 : 1, 0}; + double[] up = new double[3]; + VectorUtil.norm(zv); + VectorUtil.norm(rv); + + VectorUtil.cross(zv, rv, up); + VectorUtil.cross(zv, up, rv); + VectorUtil.cross(zv, rv, up); + mUpVector = up; + mScreenDim = new int[]{w, h}; + calcMatrix(); + } + + public void lookAt(Object3D tri, float[] voxelDim, int w, int h) { + calcLook(tri, voxelDim, w, h); + + mEyePoint = new double[]{mLookPoint[0] + mScreenWidth, mLookPoint[1] + mScreenWidth, mLookPoint[2] + mScreenWidth}; + double[] zv = new double[]{-1, -1, -1}; + double[] rv = new double[]{1, 1, 0}; + double[] up = new double[3]; + VectorUtil.norm(zv); + VectorUtil.norm(rv); + + VectorUtil.cross(zv, rv, up); + VectorUtil.cross(zv, up, rv); + VectorUtil.cross(zv, rv, up); + mUpVector = up; + mScreenDim = new int[]{w, h}; + calcMatrix(); + } + + float mStartx, mStarty; + float mPanStartX = Float.NaN, mPanStartY = Float.NaN; + Matrix mStartMatrix; + double[] mStartV = new double[3]; + double[] mMoveToV = new double[3]; + double[] mStartEyePoint; + double[] mStartUpVector; + double dist = 0; + Quaternion mQ = new Quaternion(0, 0, 0, 0); + + public void trackBallUP(float x, float y) { + + } + + public void trackBallDown(float x, float y) { + dist = calCameraDistance(); + mStartx = x; + mStarty = y; + ballToVec(x, y, mStartV); + mStartEyePoint = Arrays.copyOf(mEyePoint, m.length); + mStartUpVector = Arrays.copyOf(mUpVector, m.length); + mStartMatrix = new Matrix(this); + mStartMatrix.makeRotation(); + } + + public void trackBallMove(float x, float y) { + if (mStartx == x && mStarty == y) { + return; + } + ballToVec(x, y, mMoveToV); + + double angle = Quaternion.calcAngle(mStartV, mMoveToV); + double[] axis = Quaternion.calcAxis(mStartV, mMoveToV); + + axis = mStartMatrix.vecmult(axis); + + mQ.set(angle, axis); + + VectorUtil.sub(mLookPoint, mStartEyePoint, mEyePoint); + + mEyePoint = mQ.rotateVec(mEyePoint); + mUpVector = mQ.rotateVec(mStartUpVector); + VectorUtil.normalize(mEyePoint); + VectorUtil.mult(mEyePoint,dist,mEyePoint); + VectorUtil.sub(mLookPoint, mEyePoint, mEyePoint); + calcMatrix(); + + } + + public void panDown(float x, float y) { + mPanStartX = x; + mPanStartY = y; + } + + public void panMove(float x, float y) { + double scale = mScreenWidth / mScreenDim[0]; + if (Float.isNaN(mPanStartX)) { + mPanStartX = x; + mPanStartY = y; + } + double dx = scale * (x - mPanStartX); + double dy = scale * (y - mPanStartY); + VectorUtil.sub(mEyePoint, mLookPoint, mTmp1); + VectorUtil.normalize(mTmp1); + VectorUtil.cross(mTmp1, mUpVector, mTmp1); + VectorUtil.madd(mTmp1, dx, mEyePoint, mEyePoint); + VectorUtil.madd(mTmp1, dx, mLookPoint, mLookPoint); + VectorUtil.madd(mUpVector, dy, mEyePoint, mEyePoint); + VectorUtil.madd(mUpVector, dy, mLookPoint, mLookPoint); + mPanStartY = y; + mPanStartX = x; + calcMatrix(); + } + + public void panUP() { + mPanStartX = Float.NaN; + mPanStartY = Float.NaN; + } + + void ballToVec(float x, float y, double[] v) { + float ballRadius = Math.min(mScreenDim[0], mScreenDim[1]) * .4f; + double cx = mScreenDim[0] / 2.; + double cy = mScreenDim[1] / 2.; + + double dx = (cx - x) / ballRadius; + double dy = (cy - y) / ballRadius; + double scale = dx * dx + dy * dy; + if (scale > 1) { + scale = Math.sqrt(scale); + dx = dx / scale; + dy = dy / scale; + } + + double dz = Math.sqrt(Math.abs(1 - (dx * dx + dy * dy))); + v[0] = dx; + v[1] = dy; + v[2] = dz; + VectorUtil.normalize(v); + } + + +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/AxisBox.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/AxisBox.java new file mode 100644 index 00000000..a3218129 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/AxisBox.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2023 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 android.support.constraintLayout.extlib.graph3d.objects; + +import android.support.constraintLayout.extlib.graph3d.Object3D; +import android.support.constraintLayout.extlib.graph3d.Scene3D; +import android.support.constraintLayout.extlib.graph3d.VectorUtil; + + +/** + * Draws box along the axis + */ +public class AxisBox extends Object3D { + int color = 0xFF1010FF; + public AxisBox() { + mType = 1; + } + + public void setRange(float minX, float maxX, float minY, float maxY, float minZ, float maxZ) { + mMinX = minX; + mMaxX = maxX; + mMinY = minY; + mMaxY = maxY; + mMinZ = minZ; + mMaxZ = maxZ; + buildBox(); + } + + void buildBox() { + vert = new float[8 * 3]; // cube 8 corners + tVert = new float[vert.length]; + for (int i = 0; i < 8; i++) { + vert[i * 3] = ((i & 1) == 0) ? mMinX : mMaxX; // X + vert[i * 3 + 1] = (((i >> 1) & 1) == 0) ? mMinY : mMaxY; // Y + vert[i * 3 + 2] = (((i >> 2) & 1) == 0) ? mMinZ : mMaxZ; // Z + } + + + index = new short[6 * 2 * 3]; // 6 sides x 2 triangles x 3 points per triangle + short []sides = { // pattern of clockwise triangles around cube + 0, 2, 1, 3, 1, 2, + 0, 1, 4, 5, 4, 1, + 0, 4, 2, 6, 2, 4, + 7, 6, 5, 4, 5, 6, + 7, 3, 6, 2, 6, 3, + 7, 5, 3, 1, 3, 5 + }; + index = new short[sides.length]; + for (int i = 0; i < sides.length; i++) { + index[i] = (short) (3*sides[i]); + } + } + + public void render_old(Scene3D s, float[] zbuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + + float height = (vert[p1 + 2] + vert[p3 + 2] + vert[p2 + 2]) / 3; + int val = (int) (255 * Math.abs(height)); + + + Scene3D.drawline(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f); +// Scene3D.drawline(zbuff, img,color, w, h, +// tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, +// tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f); +// Scene3D.drawline(zbuff, img,color, w, h, +// tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f, +// tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f); + } + } + + public void render(Scene3D s, float[] zbuff, int[] img, int w, int h) { + raster_color(s, zbuff, img, w, h); + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + + boolean front = Scene3D.isBackface( + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2]); + if (front) { + Scene3D.drawline(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f); + Scene3D.drawline(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f); + continue; + } + + Scene3D.drawline(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f); + drawTicks(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p2], tVert[p2 + 1], tVert[p2 + 2] - 0.01f, + tVert[p3]-tVert[p1], tVert[p3 + 1]-tVert[p1+1], tVert[p3 + 2] -tVert[p1+2] + ); + Scene3D.drawline(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f); + drawTicks(zbuff, img, color, w, h, + tVert[p1], tVert[p1 + 1], tVert[p1 + 2] - 0.01f, + tVert[p3], tVert[p3 + 1], tVert[p3 + 2] - 0.01f, + tVert[p2]-tVert[p1], tVert[p2 + 1]-tVert[p1+1], tVert[p2 + 2] -tVert[p1+2]); + + } + } + public static void drawTicks(float[] zbuff, int[] img, int color, int w, int h, + float p1x, float p1y, float p1z, + float p2x, float p2y, float p2z, + float nx, float ny, float nz + ) { + + float tx = nx/10; + float ty = ny/10; + float tz = nz/10; + for (float f = 0; f <= 1; f+=0.1f) { + float px = p1x + f*(p2x-p1x); + float py = p1y + f*(p2y-p1y); + float pz = p1z + f*(p2z-p1z); + Scene3D.drawline(zbuff, img, color, w, h, + px,py,pz - 0.01f, + px+tx,py+ty,pz+tz - 0.01f); + } + } + + + + float[] screen = {0, 0, -1}; + + void raster_color(Scene3D s, float[] zbuff, int[] img, int w, int h) { + for (int i = 0; i < index.length; i += 3) { + int p1 = index[i]; + int p2 = index[i + 1]; + int p3 = index[i + 2]; + boolean back = Scene3D.isBackface( + tVert[p1], tVert[p1 + 1], tVert[p1 + 2], + tVert[p2], tVert[p2 + 1], tVert[p2 + 2], + tVert[p3], tVert[p3 + 1], tVert[p3 + 2]); + if (back) continue; + + VectorUtil.triangleNormal(tVert, p1, p3, p2, s.tmpVec); + float ss = VectorUtil.dot(s.tmpVec, screen); + float defuse = VectorUtil.dot(s.tmpVec, s.mTransformedLight); + float ambient = 0.5f; + + float bright = Math.min(Math.max(0, defuse + ambient), 1); + float hue = 0.4f; + float sat = 0.1f; + + int col = Scene3D.hsvToRgb(hue, sat, bright); + Scene3D.triangle(zbuff, img, col, w, h, tVert[p1], tVert[p1 + 1], + tVert[p1 + 2], tVert[p2], tVert[p2 + 1], + tVert[p2 + 2], tVert[p3], tVert[p3 + 1], + tVert[p3 + 2]); + } + } + +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/Surface3D.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/Surface3D.java new file mode 100644 index 00000000..3fe49553 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/graph3d/objects/Surface3D.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2023 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 android.support.constraintLayout.extlib.graph3d.objects; + + +import android.support.constraintLayout.extlib.graph3d.Object3D; + +/** + * Plots a surface based on Z = f(X,Y) + */ +public class Surface3D extends Object3D { + private Function mFunction; + private float mZoomZ = 1; + int mSize = 100; + float time = 0; + public void setRange(float minX, float maxX, float minY, float maxY, float minZ, float maxZ) { + mMinX = minX; + mMaxX = maxX; + mMinY = minY; + mMaxY = maxY; + mMinZ = minZ; + mMaxZ = maxZ; + computeSurface(time, Float.isNaN(mMinZ)); + } + + public void setArraySize(int size) { + mSize = size; + computeSurface(time,false); + } + + public interface Function { + float eval(float x, float y, float t); + } + + public Surface3D(Function func) { + mFunction = func; + } + + public void computeSurface(float t,boolean resetZ) { + + int n = (mSize + 1) * (mSize + 1); + makeVert(n); + makeIndexes(mSize * mSize * 2); + calcSurface(t, resetZ); + } + public void calcSurface(float t, boolean resetZ) { + float min_x = mMinX; + float max_x = mMaxX; + float min_y = mMinY; + float max_y = mMaxY; + float min_z = Float.MAX_VALUE; + float max_z = -Float.MAX_VALUE; + + + int count = 0; + + for (int iy = 0; iy <= mSize; iy++) { + float y = min_y + iy * (max_y - min_y) / (mSize); + for (int ix = 0; ix <= mSize; ix++) { + float x = min_x + ix * (max_x - min_x) / (mSize); + float delta = 0.001f; + float dx = (mFunction.eval(x+delta, y, t)- mFunction.eval(x-delta, y, t))/(2*delta); + float dy = (mFunction.eval(x, y+delta, t)- mFunction.eval(x, y-delta, t))/(2*delta); + float dz = 1; + float norm = (float) Math.sqrt(dz*dz+dx*dx+dy*dy); + dx/=norm; + dy/=norm; + dz/=norm; + normal[count] =dx; + vert[count++] = x; + normal[count] = dy; + vert[count++] = y; + normal[count] = -dz; + float z = mFunction.eval(x, y, t); + + if (Float.isNaN(z) || Float.isInfinite(z)) { + float epslonX = 0.000005232f; + float epslonY = 0.00000898f; + z = mFunction.eval(x + epslonX, y + epslonY, t); + } + vert[count++] = z; + + if (Float.isNaN(z)) { + continue; + } + + if (Float.isInfinite(z)) { + continue; + } + min_z = Math.min(z, min_z); + max_z = Math.max(z, max_z); + } + if (resetZ) { + mMinZ = min_z; + mMaxZ = max_z; + } + } + // normalize range in z + float xrange = mMaxX - mMinX; + float yrange = mMaxY - mMinY; + float zrange = mMaxZ - mMinZ; + if (zrange != 0 && resetZ) { + float xyrange = (xrange + yrange) / 2; + float scalez = xyrange / zrange; + + for (int i = 0; i < vert.length; i += 3) { + float z = vert[i + 2]; + if (Float.isNaN(z) || Float.isInfinite(z)) { + if (i > 3) { + z = vert[i - 1]; + } else { + z = vert[i + 5]; + } + } + vert[i + 2] = z * scalez * mZoomZ; + } + if (resetZ) { + mMinZ *= scalez; + mMaxZ *= scalez; + } + } + count = 0; + for (int iy = 0; iy < mSize; iy++) { + for (int ix = 0; ix < mSize; ix++) { + int p1 = 3 * (ix + iy * (mSize + 1)); + int p2 = 3 * (1 + ix + iy * (mSize + 1)); + int p3 = 3 * (ix + (iy + 1) * (mSize + 1)); + int p4 = 3 * (1 + ix + (iy + 1) * (mSize + 1)); + index[count++] = (short)p1; + index[count++] = (short)p2; + index[count++] = (short)p3; + + index[count++] = (short)p4; + index[count++] = (short)p3; + index[count++] = (short)p2; + + } + } + } + +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/UiDelegate.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/UiDelegate.java new file mode 100644 index 00000000..154dd5d4 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/UiDelegate.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.variableplot; + +public interface UiDelegate { + public boolean post(Runnable runnable); + + public boolean postDelayed(Runnable runnable, long delayMillis); + + public void invalidate(); + + public int getWidth(); + + public int getHeight(); +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/Vp.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/Vp.java new file mode 100644 index 00000000..fe17ee54 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/Vp.java @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.variableplot; + +import java.util.HashMap; + +public class Vp { + static HashMap channels = new HashMap<>(); + + static class MSChannel { + int mSize = 32000; + String mName; + int mLast = 0; + int mWritten = 0; + long[] mTime = new long[mSize]; + + private MSChannel() { + + } + + private MSChannel(int size) { + mSize = size; + mTime = new long[mSize]; + } + + int getLatest() { + if (mWritten == 0) { + return -1; + } + return (mLast == 0 ? mSize : mLast) - 1; + } + + long getPos() { + int written; + int last; + synchronized (this) { + written = mWritten; + last = mLast; + } + return ((long) written) << 32 | last; + } + + void increment() { + synchronized (this) { + mLast++; + if (mLast == mSize) { + mLast = 0; + } + mWritten++; + } + } + + synchronized int getLast() { + return mLast; + } + } + + // ======================== Support for float ============================== + static class MSChannelFloat extends MSChannel { + float[] mData = new float[mSize]; + + MSChannelFloat(String name) { + this.mName = name; + } + + MSChannelFloat(String name, int size) { + super(size); + this.mName = name; + mData = new float[size]; + } + + void add(long time, float value) { + int last = getLast(); + mTime[last] = time; + mData[last] = value; + increment(); + } + + + public int get(long start, long[] time, float[] value) { + int count = 0; + + int written; + int last, first; + long firstTime; + long lastTime; + synchronized (this) { + written = mWritten; + last = mLast; + + if (written > mSize) { + firstTime = mTime[(last + 1) % mSize]; + } else { + firstTime = mTime[last - written]; + } + int lt = last - 1; + lastTime = mTime[lt >= 0 ? lt : lt + mSize]; + } + int startIndex = -1; + if (mWritten > mSize && mLast != mSize - 1) { // it has wrapped + if (mTime[mSize - 1] > start) { + startIndex = search(mTime, last, mSize - 1, start); + } else { + startIndex = search(mTime, 0, last, start); + } + } else { + startIndex = search(mTime, 0, last, start); + } + return getIndex(startIndex, time, value); + } + + static int search(long arr[], int low, int high, long key) { + int pos = -1; + while (low <= high) { + pos = low + (high - low) / 2; + if (arr[pos] == key) + return pos; + + if (arr[pos] < key) + low = pos + 1; + else + high = pos - 1; + } + return pos; + } + + public void findIndex(long[] time, int start, int end, int key) { + + } + + + public int getIndex(int startIndex, long[] time, float[] value) { + int len = time.length; + if (len + startIndex < mSize) { + System.arraycopy(mTime, startIndex, time, 0, len); + System.arraycopy(mData, startIndex, value, 0, len); + return (len + startIndex > mLast) ? mLast - startIndex : len; + } else { + int block1len = mSize - startIndex; + System.arraycopy(mTime, startIndex, time, 0, block1len); + System.arraycopy(mData, startIndex, value, 0, block1len); + int copyLen = (mLast > len - block1len) ? len - block1len : mLast; + System.arraycopy(mTime, 0, time, block1len, copyLen); + System.arraycopy(mData, 0, value, block1len, copyLen); + return block1len + copyLen; + } + + } + + int getLatest(long[] time, float[] value) { + int written; + int last; + synchronized (this) { + written = mWritten; + last = mLast; + } + int maxLen = time.length; + if (written == 0) return -1; + if (maxLen > written) { + maxLen = (int) mWritten; + } + if (maxLen > mSize) { + maxLen = mSize; + } + if (last - maxLen < 0) { // copy [0....last, size-(maxLen-last)...size-1] + int copy1src = 0; + int copy1Dest = maxLen - last; + int copy1len = last; + + System.arraycopy(mTime, copy1src, time, copy1Dest, copy1len); + System.arraycopy(mData, copy1src, value, copy1Dest, copy1len); + int copy2src = mSize - (maxLen - last); + int copy2Dest = 0; + int copy2len = maxLen - last; + + System.arraycopy(mTime, copy2src, time, copy2Dest, copy2len); + System.arraycopy(mData, copy2src, value, copy2Dest, copy2len); + } else { + int copy1src = last - maxLen; + int copy1Dest = 0; + int copy1len = maxLen; + System.arraycopy(mTime, copy1src, time, copy1Dest, copy1len); + System.arraycopy(mData, copy1src, value, copy1Dest, copy1len); + } + + return maxLen; + } + } + // ======================== Support for float ============================== + static class MSChannelState extends MSChannel { + String[] mEvent = new String[mSize]; + int[] mState = new int[mSize]; + + MSChannelState(String name) { + this.mName = name; + } + + MSChannelState(String name, int size) { + super(size); + this.mName = name; + mEvent = new String[size]; + mState = new int[size]; + } + + void add(long time, String event, int state) { + int last = getLast(); + mTime[last] = time; + mEvent[last] = event; + mState[last] = state; + increment(); + } + + + public int get(long start, long[] time, String[] event, int []states) { + int count = 0; + + int written; + int last, first; + long firstTime; + long lastTime; + synchronized (this) { + written = mWritten; + last = mLast; + + if (written > mSize) { + firstTime = mTime[(last + 1) % mSize]; + } else { + firstTime = mTime[last - written]; + } + int lt = last - 1; + lastTime = mTime[lt >= 0 ? lt : lt + mSize]; + } + int startIndex = -1; + if (mWritten > mSize && mLast != mSize - 1) { // it has wrapped + if (mTime[mSize - 1] > start) { + startIndex = search(mTime, last, mSize - 1, start); + } else { + startIndex = search(mTime, 0, last, start); + } + } else { + startIndex = search(mTime, 0, last, start); + } + return getIndex(startIndex, time, event, states); + } + + static int search(long arr[], int low, int high, long key) { + int pos = -1; + while (low <= high) { + pos = low + (high - low) / 2; + if (arr[pos] == key) + return pos; + + if (arr[pos] < key) + low = pos + 1; + else + high = pos - 1; + } + return pos; + } + + public void findIndex(long[] time, int start, int end, int key) { + + } + + + public int getIndex(int startIndex, long[] time, String[] event, int []states) { + int len = time.length; + if (len + startIndex < mSize) { + System.arraycopy(mTime, startIndex, time, 0, len); + System.arraycopy(mEvent, startIndex, event, 0, len); + System.arraycopy(mState, startIndex, states, 0, len); + return (len + startIndex > mLast) ? mLast - startIndex : len; + } else { + int block1len = mSize - startIndex; + System.arraycopy(mTime, startIndex, time, 0, block1len); + System.arraycopy(mEvent, startIndex, event, 0, block1len); + System.arraycopy(mState, startIndex, states, 0, block1len); + int copyLen = (mLast > len - block1len) ? len - block1len : mLast; + System.arraycopy(mTime, 0, time, block1len, copyLen); + System.arraycopy(mEvent, 0, event, block1len, copyLen); + System.arraycopy(mState, 0, states, block1len, copyLen); + return block1len + copyLen; + } + + } + + int getLatest(long[] time, String[] event, int []states) { + int written; + int last; + synchronized (this) { + written = mWritten; + last = mLast; + } + int maxLen = time.length; + if (written == 0) return -1; + if (maxLen > written) { + maxLen = (int) mWritten; + } + if (maxLen > mSize) { + maxLen = mSize; + } + if (last - maxLen < 0) { // copy [0....last, size-(maxLen-last)...size-1] + int copy1src = 0; + int copy1Dest = maxLen - last; + int copy1len = last; + + System.arraycopy(mTime, copy1src, time, copy1Dest, copy1len); + System.arraycopy(mEvent, copy1src, event, copy1Dest, copy1len); + System.arraycopy(mState, copy1src, states, copy1Dest, copy1len); + int copy2src = mSize - (maxLen - last); + int copy2Dest = 0; + int copy2len = maxLen - last; + + System.arraycopy(mTime, copy2src, time, copy2Dest, copy2len); + System.arraycopy(mEvent, copy2src, event, copy2Dest, copy2len); + System.arraycopy(mState, copy2src, states, copy2Dest, copy2len); + } else { + int copy1src = last - maxLen; + int copy1Dest = 0; + int copy1len = maxLen; + System.arraycopy(mTime, copy1src, time, copy1Dest, copy1len); + System.arraycopy(mEvent, copy1src, event, copy1Dest, copy1len); + System.arraycopy(mState, copy1src, states, copy1Dest, copy1len); + } + + return maxLen; + } + } + + public static void initFloatChannel(String channel, int size) { + channels.put(channel, new MSChannelFloat(channel, size)); + } + + public static void initStateChannel(String channel, int size) { + channels.put(channel, new MSChannelState(channel, size)); + } + public static void send(String channel, String event, int state) { + send(channel, System.nanoTime(), event,state); + } + + public static void send(String channel, float value) { + send(channel, System.nanoTime(), value); + } + + + public static void send(String channel, long time, float value) { + MSChannelFloat c = (MSChannelFloat) channels.get(channel); + if (c == null) { + c = new MSChannelFloat(channel); + channels.put(channel, c); + } + c.add(time, value); + } + + public static void send(String channel, long time, String event, int state) { + MSChannelState c = (MSChannelState) channels.get(channel); + if (c == null) { + c = new MSChannelState(channel); + channels.put(channel, c); + } + c.add(time, event,state); + } + + public static int getLatest(String channel, long[] times, float[] values) { + MSChannelFloat c = (MSChannelFloat) channels.get(channel); + if (c == null) { + return -1; + } + return c.getLatest(times, values); + } + public static int getLatest(String channel, long[] times, String []event, int []state) { + MSChannelState c = (MSChannelState) channels.get(channel); + if (c == null) { + return -1; + } + return c.getLatest(times, event, state); + } + public static int totalWritten(String channel) { + MSChannel c = (MSChannel) channels.get(channel); + if (c == null) { + return -1; + } + return c.mWritten; + } + + public static int getAfter(String channel, long time, long[] times, float[] values) { + MSChannelFloat c = (MSChannelFloat) channels.get(channel); + if (c == null) { + return -1; + } + return c.get(time, times, values); + } + + public static void sinWaveGen(String channel) { + Thread t = new Thread() { + @Override + public void run() { + + long[] time = new long[100]; + float[] value = new float[100]; + for (int i = 0; i < 100000000; i++) { + long nt = System.nanoTime(); + float v = (float) (Math.sin(nt * 1E-9 * Math.PI) * Math.sin(nt * 1E-9 * Math.PI / 40)); + Vp.send(channel, nt, v); + + try { + Thread.sleep(10); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }; + t.setDaemon(false); + t.start(); + } + + private static final HashMap last_fps_time = new HashMap<>(); + + public static void fps(String channel) { + long[] ans = last_fps_time.get(channel); + if (ans == null) { + ans = new long[1]; + last_fps_time.put(channel, ans); + ans[0] = System.nanoTime(); + } + long now = System.nanoTime(); + float duration = (now - ans[0]) * 1E-9f; + + if (duration < 1 / 500f) { + send(channel, now, 0); + } else { + send(channel, now, 1 / duration); + } + ans[0] = now; + + } + + // ======================= END Support for Floats================================== + + public static void main(String[] args) throws InterruptedException { + long last = System.nanoTime(); + Thread t = new Thread() { + @Override + public void run() { + + long[] time = new long[100]; + float[] value = new float[100]; + for (int i = 0; i < 100000000; i++) { + int len = getLatest("bob", time, value); + if (len == -1) { + System.out.println("skip"); + try { + Thread.sleep(0, 100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + continue; + } + long current = System.nanoTime(); + System.out.println(" " + len + " " + (current - time[len - 1]) * 1E-6f + "ms " + value[len - 1]); + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + }; + t.setDaemon(false); + t.start(); + int sample = 10000000; + for (long i = 0; i < 100000000000L; i++) { + send("bob", i); + // Thread.sleep(0,100); + if (i % sample == 0) { + long now = System.nanoTime(); + + System.out.println(i + " per sec =" + sample / ((now - last) * 1E-9)); + last = now; + } + + } + + } +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraph.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraph.java new file mode 100644 index 00000000..e90b5a32 --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraph.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.variableplot; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.Nullable; + +public class VpGraph extends View implements UiDelegate { + private VpGraphCore mGraphCore; + + public VpGraph(Context context) { + super(context); + init(); + } + + public VpGraph(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public VpGraph(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + public void init() { + mGraphCore = new VpGraphCore(this); + } + + public VpGraphCore getGraphCore() { + return mGraphCore; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + return mGraphCore.onTouchEvent(event); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + mGraphCore.onDraw(canvas); + } +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraphCore.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraphCore.java new file mode 100644 index 00000000..40b8242c --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/variableplot/VpGraphCore.java @@ -0,0 +1,499 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.variableplot; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; + +import androidx.annotation.Nullable; + +import java.text.DecimalFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class VpGraphCore { + private static final String FPS_STRING = "onDraw"; + + List mPlots = new ArrayList<>(); + final static int MAX_BUFF = 2000; + private long[] mTime = new long[MAX_BUFF]; + private float[] mValue = new float[MAX_BUFF]; + float duration = 10; // seconds + float mMinY = 0; + float mMaxY = 1; + float mMinX = 0; + float mMaxX = duration + mMinX; + float axisLeft = 100; + float axisTop = 100; + float axisRight = 100; + float axisBottom = 100; + Paint mAxisPaint = new Paint(); + Paint mLinePaint = new Paint(); + Paint mGridPaint = new Paint(); + Rect mBounds = new Rect(); + long start = -1; // show the latest; + boolean mGraphSelfFps = false; + int sampleDelay = 15; // ms between samples. + private boolean mLiveSample = true; + private long mStartTime; + private float mLineX = Float.NaN; + public boolean debug = false; + static class Data { + float[] mX = new float[MAX_BUFF]; + float[] mY = new float[MAX_BUFF]; + Paint paint = new Paint(); + Path path = new Path(); + int mLength; + String mTitle; + float lastLabelYPos = Float.NaN; + float lastLabelXPos = Float.NaN; + + Data(String title) { + mTitle = title; + mLength = -1; + paint.setStyle(Paint.Style.STROKE); + } + + void plot(Canvas canvas, VpGraphCore graph, int w, int h) { + path.reset(); + float scaleX = graph.getScaleX(w, h); + float scaleY = graph.getScaleY(w, h); + float offX = graph.getOffsetX(w, h); + float offY = graph.getOffsetY(w, h); + boolean first = true; + for (int i = 0; i < mLength; i++) { + if ((i == mLength - 1 || mX[i + 1] >= graph.mMinX) && mX[i]<=graph.mMaxY) { + + float x = mX[i] * scaleX + offX; + float y = mY[i] * scaleY + offY; + if (first) { + path.moveTo(x, y); + first = false; + } else { + path.lineTo(x, y); + } + } + } + canvas.drawPath(path, paint); + } + + public int findClosestX(float x) { + int low = 0; + int high = mLength - 1; + int pos = -1; + while (low <= high) { + pos = low + (high - low) / 2; + if (mX[pos] == x) + return pos; + + if (mX[pos] < x) + low = pos + 1; + else + high = pos - 1; + } + return pos; + } + + + } + + private final UiDelegate mUiDelegate; + + public VpGraphCore(UiDelegate uiDelegate) { + mUiDelegate = uiDelegate; + init(); + mAxisPaint.setColor(Color.BLACK); + } + + public void init() { + mUiDelegate.post(this::listenToChannels); + mAxisPaint.setTextSize(32); + mAxisPaint.setStrokeWidth(3); + mAxisPaint.setColor(Color.BLUE); + mGridPaint.setTextSize(32); + mGridPaint.setStrokeWidth(1); + mLinePaint.setColor(Color.RED); + mLinePaint.setStrokeWidth(3); + mLinePaint.setTextSize(64); + + } + float mDownX; + + public boolean onTouchEvent(MotionEvent event) { + int action = event.getAction() & MotionEvent.ACTION_MASK; + int count = event.getPointerCount(); + // Log.v("Main", ">>>> count " + count + " " +event); + switch (action) { + case MotionEvent.ACTION_MOVE: + if (count == 2) { + float drag = event.getX(0) + event.getX(1); + drag = (drag+mDownX)/2; + mStartTime += drag / getScaleX(mUiDelegate.getWidth(), mUiDelegate.getHeight()); + listenToChannels(); + Log.v("Main", ">>>> drag "+ drag ); + mLineX = Float.NaN; + } else { + mLineX = event.getX(); + Log.v("Main", ">>>> ACTION_MOVE " + event.getX() + " "); + mUiDelegate.invalidate(); + } + break; + case MotionEvent.ACTION_DOWN: + if (count == 2) { + mLiveSample = false; + mDownX = (event.getX(0) + event.getX(1)); + Log.v("Main", count+">>>> drag on" +mDownX + " "); + + mLineX = Float.NaN; + } else { + mLineX = event.getX(); + Log.v("Main", count+">>>> ACTION_DOWN " + event.getX()); + mUiDelegate.invalidate(); + } + break; + case MotionEvent.ACTION_UP: + Log.v("Main", ">>>> ACTION_UP " + event.getX()); + + mLineX = Float.NaN; + for (VpGraphCore.Data mPlot : mPlots) { + mPlot.lastLabelYPos = Float.NaN; + mPlot.lastLabelXPos = Float.NaN; + } + mUiDelegate.invalidate(); + break; + case MotionEvent.ACTION_POINTER_DOWN: + if (count == 2) { + + mLiveSample = false; + mDownX = (event.getX(0) + event.getX(1)); + Log.v("Main", count+">>>> ACTION_POINTER_DOWN" +mDownX + " "+ mLiveSample); + + mLineX = Float.NaN; + } + break; + case MotionEvent.ACTION_POINTER_UP: + Log.v("Main", ">>>> ACTION_POINTER_UP" + event.getX()); + if (event.getEventTime() - event.getDownTime() < 400) { + Log.v("Main", ">>>> false" ); + mLiveSample = true; + listenToChannels(); + } + + default: + Log.v("Main", ">>>> def " + event.getEventTime()); + } + return true; + //return super.onTouchEvent(event); + } + + public void addChannel(String str) { + mPlots.add(new VpGraphCore.Data(str)); + } + + public void setGraphFPS(boolean on) { + mGraphSelfFps = on; + if (on) { + mPlots.add(new VpGraphCore.Data(FPS_STRING)); + // mPlots.add(new Data("read")); + } else { + VpGraphCore.Data remove = null; + for (int i = 0; i < mPlots.size(); i++) { + if (mPlots.get(i).mTitle == FPS_STRING) { + remove = mPlots.get(i); + break; + } + } + if (remove != null) { + mPlots.remove(remove); + } + } + } + + + private void listenToChannels() { + // Vp.fps("read"); + if (mLiveSample) { + listenLive(); + return; + } + int count = mPlots.size(); + + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float maxX = -Float.MAX_VALUE; + float maxY = -Float.MAX_VALUE; + for (int i = 0; i < count; i++) { + VpGraphCore.Data p = mPlots.get(i); + String channel = p.mTitle; + + + p.mLength = Vp.getAfter(channel, mStartTime, mTime, mValue); + + if (p.mLength == -1) { + continue; + } + + for (int j = 0; j < p.mLength; j++) { + float x = (mTime[j] - mStartTime) * 1E-9f; + p.mX[j] = x; + minX = Math.min(x, minX); + maxX = Math.max(x, maxX); + + float y = mValue[j]; + minY = Math.min(y, minY); + maxY = Math.max(y, maxY); + p.mY[j] = y; + } + Log.v("main", p.mTitle+" "+ minX+" -> "+maxX); + } + + minX = 0; + + maxX = minX + duration; + Log.v("main", "Total "+ minX+" -> "+maxX); + + updateDataRange(minX, maxX, minY, maxY); + + mUiDelegate.invalidate(); + } + + private void listenLive() { + + int count = mPlots.size(); + + mStartTime = System.nanoTime() - (long) (((double) duration) * 1000000000L); + + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float maxX = -Float.MAX_VALUE; + float maxY = -Float.MAX_VALUE; + for (int i = 0; i < count; i++) { + VpGraphCore.Data p = mPlots.get(i); + String channel = p.mTitle; + + p.mLength = Vp.getLatest(channel, mTime, mValue); + + if (p.mLength == -1) { + continue; + } + + for (int j = 0; j < p.mLength; j++) { + float x = (mTime[j] - mStartTime) * 1E-9f; + p.mX[j] = x; + minX = Math.min(x, minX); + maxX = Math.max(x, maxX); + + float y = mValue[j]; + minY = Math.min(y, minY); + maxY = Math.max(y, maxY); + p.mY[j] = y; + } + } + if (minX == Float.MAX_VALUE || Float.isNaN(minX)) { + updateDataRange(0, 10, -1, 1); + } else { + updateDataRange(minX, maxX, minY, maxY); + } + + mUiDelegate.invalidate(); + mUiDelegate.postDelayed(this::listenToChannels, sampleDelay); + } + + private void updateDataRange(float minX, float maxX, float minY, float maxY) { + minX = maxX - duration; + // fast to expand slow to contract + float factor = 10; + mMaxY = (mMaxY > maxY) ? (mMaxY + maxY) / 2 : (mMaxY * factor + maxY) / (factor + 1); + mMinY = (mMinY < minY) ? (mMinY + minY) / 2 : (mMinY * factor + minY) / (factor + 1); + mMinX = (mMinX + minX) / 2; + mMaxX = duration + mMinX; + } + + public void onDraw(Canvas canvas) { + int w = mUiDelegate.getWidth(); + int h = mUiDelegate.getHeight(); + drawAxis(canvas, w, h); + drawGrid(canvas, w, h); + for (VpGraphCore.Data p : mPlots) { + p.plot(canvas, this, w, h); + } + if (mGraphSelfFps || debug) { + Vp.fps(FPS_STRING); + } + + drawTouchLine(canvas, w, h); + } + + private void drawGrid(Canvas canvas, int w, int h) { + double ticksX = calcTick(w, mMaxX - mMinX); + double ticksY = calcTick(h, mMaxY - mMinY); + float minX = (float) (ticksX * Math.ceil((mMinX + ticksX / 100) / ticksX)); + float maxX = (float) (ticksX * Math.floor(mMaxX / ticksX)); + // Log.v("MAIN", " X " + ticksX + " " + minX + " - " + maxX); + int count = 0; + float scaleX = getScaleX(w, h); + float offX = getOffsetX(w, h); + int txtPad = 4; + + for (float x = minX; x <= maxX; x += ticksX) { + float xp = scaleX * x + offX; + canvas.drawLine(xp, axisTop, xp, h - axisBottom, mGridPaint); + if (x==(int)x) { + String str = df.format(x); + mAxisPaint.getTextBounds(str, 0, str.length(), mBounds); + canvas.drawText(str, xp - mBounds.width()/2 , h-axisBottom+txtPad+mBounds.height(), mGridPaint); + } + count++; + } + + float minY = (float) (ticksY * Math.ceil((mMinY + ticksY / 100) / ticksY)); + float maxY = (float) (ticksY * Math.floor(mMaxY / ticksY)); + // Log.v("MAIN", " Y " + ticksY + " " + minY + " - " + maxY); + + float offY = getOffsetY(w, h); + float scaleY = getScaleY(w, h); + count = 0; + + for (float y = minY; y <= maxY; y += ticksY) { + float yp = scaleY * y + offY; + canvas.drawLine(axisLeft, yp, w - axisRight, yp, mGridPaint); + + if ((count & 1) == 1 && (y + ticksY) < maxY) { + String str = df.format(y); + mAxisPaint.getTextBounds(str, 0, str.length(), mBounds); + canvas.drawText(str, axisLeft - mBounds.width() - txtPad*2, yp, mGridPaint); + } + count++; + } + } + + DecimalFormat df = new DecimalFormat("0.0"); + + void drawTouchLine(Canvas canvas, int w, int h) { + if (Float.isNaN(mLineX)) { + return; + } + if (mLineX < axisLeft) { + mLineX = axisLeft; + } else if (mLineX > (w - axisRight)) { + mLineX = w - axisRight; + } + float dataPos = (mLineX - getOffsetX(w, h)) / getScaleX(w, h); + float yOffset = getOffsetY(w, h); + float yScale = getScaleY(w, h); + float rad = 10; + canvas.drawLine(mLineX, axisTop, mLineX, h - axisBottom, mLinePaint); + int bottom_count = 0; + int top_count = 0; + int pad = 5; + boolean right = (mLineX < w / 2); + for (VpGraphCore.Data plot : mPlots) { + int index = plot.findClosestX(dataPos); + if (index == -1) continue; + float value = plot.mY[index]; + float y = yScale * value + yOffset; + canvas.drawRoundRect(mLineX - rad, y - rad, mLineX + rad, y + rad, rad, rad, mLinePaint); + String vString = plot.mTitle+":"+df.format(value); + mLinePaint.getTextBounds(vString, 0, vString.length(), mBounds); + float yPos = w / 2; + int gap = 60; + if (y > h / 2) { + yPos = y - gap; + bottom_count++; + } else { + top_count++; + yPos = y + gap; + } + float xPos = (right) ? mLineX + gap : mLineX - gap; + if (Float.isNaN(plot.lastLabelYPos)) { + plot.lastLabelYPos = yPos; + plot.lastLabelXPos = xPos; + } else { + plot.lastLabelYPos = (plot.lastLabelYPos * 100 + yPos) / 101; + plot.lastLabelXPos = (plot.lastLabelXPos * 100 + xPos) / 101;; + + } + xPos = plot.lastLabelXPos; + canvas.drawLine(mLineX, y, xPos , plot.lastLabelYPos, mLinePaint); + canvas.drawText(vString, right ? xPos :xPos-mBounds.width()-pad, plot.lastLabelYPos, mLinePaint); + } + } + + void drawAxis(Canvas canvas, int w, int h) { + int txtPad = 4; + canvas.drawRGB(200, 230, 255); + canvas.drawLine(axisLeft, axisTop, axisLeft, h - axisBottom, mAxisPaint); + canvas.drawLine(axisLeft, h - axisBottom, w - axisRight, h - axisBottom, mAxisPaint); + float y0 = getOffsetY(w, h); + canvas.drawLine(axisLeft, y0, w - axisRight, y0, mAxisPaint); + String str = df.format(mMaxY); + mAxisPaint.getTextBounds(str, 0, str.length(), mBounds); + canvas.drawText(str, axisLeft - mBounds.width() - txtPad, axisTop, mAxisPaint); + str = df.format(mMinY); + mAxisPaint.getTextBounds(str, 0, str.length(), mBounds); + canvas.drawText(str, axisLeft - mBounds.width() - txtPad, h - axisBottom, mAxisPaint); + } + + public float getScaleX(int w, int h) { + float rangeX = mMaxX - mMinX; + float graphSpanX = w - axisLeft - axisRight; + return graphSpanX / rangeX; + } + + public float getScaleY(int w, int h) { + float rangeY = mMaxY - mMinY; + float graphSpanY = h - axisTop - axisBottom; + return -graphSpanY / rangeY; + } + + public float getOffsetX(int w, int h) { + return axisLeft - mMinX * getScaleX(w, h); + } + + public float getOffsetY(int w, int h) { + return h - axisBottom - mMinY * getScaleY(w, h); + } + + static public double calcTick(int scr, double range) { + int aprox_x_ticks = scr / 100; + int type = 1; + double best = Math.log10(range / (aprox_x_ticks)); + double n = Math.log10(range / (aprox_x_ticks * 2)); + if (fraction(n) < fraction(best)) { + best = n; + type = 2; + } + n = Math.log10(range / (aprox_x_ticks * 5)); + if (fraction(n) < fraction(best)) { + best = n; + type = 5; + } + return type * Math.pow(10, Math.floor(best)); + } + + static double fraction(double x) { + return x - Math.floor(x); + } +} diff --git a/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/widget/LogJson.java b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/widget/LogJson.java new file mode 100644 index 00000000..8c2e614f --- /dev/null +++ b/constraintlayout/extensionLibrary/Views/src/java/com/google/constraintlayout/ext/widget/LogJson.java @@ -0,0 +1,853 @@ +/* + * Copyright (C) 2023 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 com.google.constraintlayout.ext.widget; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static androidx.constraintlayout.widget.ConstraintSet.Layout.UNSET_GONE_MARGIN; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Environment; +import android.util.AttributeSet; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.constraintlayout.motion.widget.Debug; +import androidx.constraintlayout.widget.ConstraintAttribute; +import androidx.constraintlayout.widget.ConstraintHelper; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; +import androidx.constraintlayout.widget.R; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * This is a class is a testing/debugging/logging utility to write out the constraints in JSON + * This is ONLY to be used for debugging & testing purposes + *
    + *
  • logJsonTo - defines the output log console or "fileName"
  • + *
  • logJsonMode - mode one of: + * periodic, delayed, layout or api
  • + *
  • logJsonDelay - the duration of the delay or the delay between repeated logs
  • + *
+ * logJsonTo supports: + *
    + *
  • log - logs using log.v("JSON5", ...)
  • + *
  • console - logs using System.out.println(...)
  • + *
  • [fileName] - will write to /storage/emulated/0/Download/[fileName].json5
  • + *
+ * logJsonMode modes are: + *
    + *
  • periodic - after window is attached will log every delay ms
  • + *
  • delayed - log once after delay ms
  • + *
  • layout - log every time there is a layout call
  • + *
  • api - do not automatically log developer will call writeLog
  • + *
+ * + * The defaults are: + *
    + *
  • logJsonTo="log"
  • + *
  • logJsonMode="delayed"
  • + *
  • logJsonDelay="1000"
  • + *
+ * Usage: + *

+ *
+ *  {@code
+ *      
+ *  }
+ * 
+ *

+ */ +public class LogJson extends ConstraintHelper { + private static final String TAG = "JSON5"; + private int mDelay = 1000; + private int mMode = LOG_DELAYED; + private String mLogToFile = null; + private boolean mLogConsole = true; + + public static final int LOG_PERIODIC = 1; + public static final int LOG_DELAYED = 2; + public static final int LOG_LAYOUT = 3; + public static final int LOG_API = 4; + private boolean mPeriodic = false; + public static final int LOG_TO_FILE = 0; + public static final int LOG_TO_LOG = 1; + public static final int LOG_TO_CONSOLE = 2; + + @IntDef({LOG_TO_FILE, LOG_TO_LOG, LOG_TO_CONSOLE}) + @Retention(RetentionPolicy.CLASS) + public @interface LoggingTo { + } + + @IntDef({LOG_PERIODIC, LOG_DELAYED, LOG_LAYOUT, LOG_API}) + public @interface LoggingMode { + } + + public LogJson(@androidx.annotation.NonNull Context context) { + super(context); + } + + public LogJson(@androidx.annotation.NonNull Context context, + @androidx.annotation.Nullable AttributeSet attrs) { + super(context, attrs); + initLogJson(attrs); + + } + + public LogJson(@androidx.annotation.NonNull Context context, + @androidx.annotation.Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLogJson(attrs); + } + + private void initLogJson(AttributeSet attrs) { + + if (attrs != null) { + TypedArray a = getContext().obtainStyledAttributes(attrs, + R.styleable.LogJson); + final int count = a.getIndexCount(); + for (int i = 0; i < count; i++) { + int attr = a.getIndex(i); + if (attr == R.styleable.LogJson_logJsonDelay) { + mDelay = a.getInt(attr, mDelay); + } else if (attr == R.styleable.LogJson_logJsonMode) { + mMode = a.getInt(attr, mMode); + } else if (attr == R.styleable.LogJson_logJsonTo) { + TypedValue v = a.peekValue(attr); + if (v.type == TypedValue.TYPE_STRING) { + mLogToFile = a.getString(attr); + } else { + int value = a.getInt(attr, 0); + mLogConsole = value == 2; + } + } + } + a.recycle(); + } + setVisibility(GONE); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + switch (mMode) { + case LOG_PERIODIC: + mPeriodic = true; + this.postDelayed(this::periodic, mDelay); + break; + case LOG_DELAYED: + this.postDelayed(this::writeLog, mDelay); + break; + case LOG_LAYOUT: + ConstraintLayout cl = (ConstraintLayout) getParent(); + cl.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) -> logOnLayout()); + } + } + + private void logOnLayout() { + if (mMode == LOG_LAYOUT) { + writeLog(); + } + } + + /** + * Set the duration of periodic logging of constraints + * + * @param duration the time in ms between writing files + */ + public void setDelay(int duration) { + mDelay = duration; + } + + /** + * get the duration of periodic logging of constraints + * + * @return the time in ms between writing files + */ + public int getDelay() { + return mDelay; + } + + /** + * Set the logging mode + * @param mode + */ + public void setLoggingMode(@LoggingMode int mode) { + mMode = mode; + } + + /** + * GEt the Logging Mode + * @return + */ + @LoggingMode + public int getLoggingMode() { + return mMode; + } + + /** + * Set what you are logging to + * @param value + */ + public void setLogTo(@LoggingTo int value) { + if (value == 0 && mLogToFile == null) { + mLogToFile = "Log" + (System.currentTimeMillis()) + ".JSON5"; + } else { + mLogToFile = null; + } + mLogConsole = value == LOG_TO_CONSOLE; + } + + /** + * Get how it is going to log + * @return + */ + @LoggingTo + public int getLogTo() { + return mLogToFile != null ? LOG_TO_FILE : (mLogConsole ? LOG_TO_CONSOLE : LOG_TO_LOG); + } + + /** + * + * @param file + */ + public void setLogToFile(@NonNull String file) { + mLogToFile = file; + } + + /** + * + * @return + */ + @Nullable + public String getLogToFile() { + return mLogToFile; + } + + /** + * Start periodic sampling + */ + public void periodicStart() { + if (mPeriodic) { + return; + } + mPeriodic = true; + this.postDelayed(this::periodic, mDelay); + } + + /** + * Stop periodic sampling + */ + public void periodicStop() { + mPeriodic = false; + } + + private void periodic() { + if (mPeriodic) { + writeLog(); + this.postDelayed(this::periodic, mDelay); + } + } + + /** + * This writes a JSON5 representation of the constraintSet + */ + public void writeLog() { + String str = asString((ConstraintLayout) this.getParent()); + if (mLogToFile == null) { + if (mLogConsole) { + System.out.println(str); + } else { + logBigString(str); + } + } else { + String name = toFile(str, mLogToFile); + Log.v("JSON", "\"" + name + "\" written!"); + } + } + + /** + * This writes the JSON5 description of the constraintLayout to a file named fileName.json5 + * in the download directory which can be pulled with: + * "adb pull "/storage/emulated/0/Download/" ." + * + * @param str String to write as a file + * @param fileName file name + * @return full path name of file + */ + private static String toFile(String str, String fileName) { + FileOutputStream outputStream; + if (!fileName.endsWith(".json5")) { + fileName += ".json5"; + } + try { + File down = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + File file = new File(down, fileName); + outputStream = new FileOutputStream(file); + outputStream.write(str.getBytes()); + outputStream.close(); + return file.getCanonicalPath(); + } catch (IOException e) { + return e.toString(); + } + } + + @SuppressLint("LogConditional") + private void logBigString(String str) { + int len = str.length(); + for (int i = 0; i < len; i++) { + int k = str.indexOf("\n", i); + if (k == -1) { + Log.v(TAG, str.substring(i)); + break; + } + Log.v(TAG, str.substring(i, k)); + i = k; + } + } + + /** + * Get a JSON5 String that represents the Constraints in a running ConstraintLayout + * + * @param constraintLayout its constraints are converted to a string + * @return JSON5 string + */ + private static String asString(ConstraintLayout constraintLayout) { + JsonWriter c = new JsonWriter(); + return c.constraintLayoutToJson(constraintLayout); + } + + // ================================== JSON writer============================================== + + private static class JsonWriter { + public static final int UNSET = ConstraintLayout.LayoutParams.UNSET; + ConstraintSet mSet; + Writer mWriter; + Context mContext; + int mUnknownCount = 0; + final String mLEFT = "left"; + final String mRIGHT = "right"; + final String mBASELINE = "baseline"; + final String mBOTTOM = "bottom"; + final String mTOP = "top"; + final String mSTART = "start"; + final String mEND = "end"; + private static final String INDENT = " "; + private static final String SMALL_INDENT = " "; + HashMap mIdMap = new HashMap<>(); + private static final String LOG_JSON = LogJson.class.getSimpleName(); + private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1); + HashMap mNames = new HashMap<>(); + + private static int generateViewId() { + final int max_id = 0x00FFFFFF; + for (;;) { + final int result = sNextGeneratedId.get(); + int newValue = result + 1; + if (newValue > max_id) { + newValue = 1; + } + if (sNextGeneratedId.compareAndSet(result, newValue)) { + return result; + } + } + } + + @RequiresApi(17) + private static class JellyBean { + static int generateViewId() { + return View.generateViewId(); + } + } + + String constraintLayoutToJson(ConstraintLayout constraintLayout) { + StringWriter writer = new StringWriter(); + + int count = constraintLayout.getChildCount(); + for (int i = 0; i < count; i++) { + View v = constraintLayout.getChildAt(i); + String name = v.getClass().getSimpleName(); + int id = v.getId(); + if (id == -1) { + if (android.os.Build.VERSION.SDK_INT + >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + id = JellyBean.generateViewId(); + } else { + id = generateViewId(); + } + v.setId(id); + if (!LOG_JSON.equals(name)) { + name = "noid_" + name; + } + mNames.put(id, name); + } else if (LOG_JSON.equals(name)) { + mNames.put(id, name); + } + } + writer.append("{\n"); + + writeWidgets(writer, constraintLayout); + writer.append(" ConstraintSet:{\n"); + ConstraintSet set = new ConstraintSet(); + set.clone(constraintLayout); + String name = + (constraintLayout.getId() == -1) ? "cset" : Debug.getName(constraintLayout); + try { + writer.append(name + ":"); + setup(writer, set, constraintLayout); + writeLayout(); + writer.append("\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + writer.append(" }\n"); + writer.append("}\n"); + return writer.toString(); + } + + private void writeWidgets(StringWriter writer, ConstraintLayout constraintLayout) { + writer.append("Widgets:{\n"); + int count = constraintLayout.getChildCount(); + + for (int i = -1; i < count; i++) { + View v = (i == -1) ? constraintLayout : constraintLayout.getChildAt(i); + int id = v.getId(); + if (LOG_JSON.equals(v.getClass().getSimpleName())) { + continue; + } + String name = mNames.containsKey(id) ? mNames.get(id) + : ((i == -1) ? "parent" : Debug.getName(v)); + String cname = v.getClass().getSimpleName(); + String bounds = ", bounds: [" + v.getLeft() + ", " + v.getTop() + + ", " + v.getRight() + ", " + v.getBottom() + "]},\n"; + writer.append(" " + name + ": { "); + if (i == -1) { + writer.append("type: '" + v.getClass().getSimpleName() + "' , "); + + try { + ViewGroup.LayoutParams p = (ViewGroup.LayoutParams) v.getLayoutParams(); + String wrap = "'WRAP_CONTENT'"; + String match = "'MATCH_PARENT'"; + String w = p.width == MATCH_PARENT ? match : + (p.width == WRAP_CONTENT) ? wrap : p.width + ""; + writer.append("width: " + w + ", "); + String h = p.height == MATCH_PARENT ? match : + (p.height == WRAP_CONTENT) ? wrap : p.height + ""; + writer.append("height: ").append(h); + } catch (Exception e) { + } + } else if (cname.contains("Text")) { + if (v instanceof TextView) { + writer.append("type: 'Text', label: '" + + escape(((TextView) v).getText().toString()) + "'"); + } else { + writer.append("type: 'Text' },\n"); + } + } else if (cname.contains("Button")) { + if (v instanceof Button) { + writer.append("type: 'Button', label: '" + ((Button) v).getText() + "'"); + } else + writer.append("type: 'Button'"); + } else if (cname.contains("Image")) { + writer.append("type: 'Image'"); + } else if (cname.contains("View")) { + writer.append("type: 'Box'"); + } else { + writer.append("type: '" + v.getClass().getSimpleName() + "'"); + } + writer.append(bounds); + } + writer.append("},\n"); + } + + private static String escape(String str) { + return str.replaceAll("'", "\\'"); + } + + JsonWriter() { + } + + void setup(Writer writer, + ConstraintSet set, + ConstraintLayout layout) throws IOException { + this.mWriter = writer; + this.mContext = layout.getContext(); + this.mSet = set; + set.getConstraint(2); + } + + private int[] getIDs() { + return mSet.getKnownIds(); + } + + private ConstraintSet.Constraint getConstraint(int id) { + return mSet.getConstraint(id); + } + + private void writeLayout() throws IOException { + mWriter.write("{\n"); + for (Integer id : getIDs()) { + ConstraintSet.Constraint c = getConstraint(id); + String idName = getSimpleName(id); + if (LOG_JSON.equals(idName)) { // skip LogJson it is for used to log + continue; + } + mWriter.write(SMALL_INDENT + idName + ":{\n"); + ConstraintSet.Layout l = c.layout; + if (l.mReferenceIds != null) { + StringBuilder ref = + new StringBuilder("type: '_" + idName + "_' , contains: ["); + for (int r = 0; r < l.mReferenceIds.length; r++) { + int rid = l.mReferenceIds[r]; + ref.append((r == 0) ? "" : ", ").append(getName(rid)); + } + mWriter.write(ref + "]\n"); + } + if (l.mReferenceIdString != null) { + StringBuilder ref = + new StringBuilder(SMALL_INDENT + "type: '???' , contains: ["); + String[] rids = l.mReferenceIdString.split(","); + for (int r = 0; r < rids.length; r++) { + String rid = rids[r]; + ref.append((r == 0) ? "" : ", ").append("`").append(rid).append("`"); + } + mWriter.write(ref + "]\n"); + } + writeDimension("height", l.mHeight, l.heightDefault, l.heightPercent, + l.heightMin, l.heightMax, l.constrainedHeight); + writeDimension("width", l.mWidth, l.widthDefault, l.widthPercent, + l.widthMin, l.widthMax, l.constrainedWidth); + + writeConstraint(mLEFT, l.leftToLeft, mLEFT, l.leftMargin, l.goneLeftMargin); + writeConstraint(mLEFT, l.leftToRight, mRIGHT, l.leftMargin, l.goneLeftMargin); + writeConstraint(mRIGHT, l.rightToLeft, mLEFT, l.rightMargin, l.goneRightMargin); + writeConstraint(mRIGHT, l.rightToRight, mRIGHT, l.rightMargin, l.goneRightMargin); + writeConstraint(mBASELINE, l.baselineToBaseline, mBASELINE, UNSET, + l.goneBaselineMargin); + writeConstraint(mBASELINE, l.baselineToTop, mTOP, UNSET, l.goneBaselineMargin); + writeConstraint(mBASELINE, l.baselineToBottom, + mBOTTOM, UNSET, l.goneBaselineMargin); + + writeConstraint(mTOP, l.topToBottom, mBOTTOM, l.topMargin, l.goneTopMargin); + writeConstraint(mTOP, l.topToTop, mTOP, l.topMargin, l.goneTopMargin); + writeConstraint(mBOTTOM, l.bottomToBottom, mBOTTOM, l.bottomMargin, + l.goneBottomMargin); + writeConstraint(mBOTTOM, l.bottomToTop, mTOP, l.bottomMargin, l.goneBottomMargin); + writeConstraint(mSTART, l.startToStart, mSTART, l.startMargin, l.goneStartMargin); + writeConstraint(mSTART, l.startToEnd, mEND, l.startMargin, l.goneStartMargin); + writeConstraint(mEND, l.endToStart, mSTART, l.endMargin, l.goneEndMargin); + writeConstraint(mEND, l.endToEnd, mEND, l.endMargin, l.goneEndMargin); + + writeVariable("horizontalBias", l.horizontalBias, 0.5f); + writeVariable("verticalBias", l.verticalBias, 0.5f); + + writeCircle(l.circleConstraint, l.circleAngle, l.circleRadius); + + writeGuideline(l.orientation, l.guideBegin, l.guideEnd, l.guidePercent); + writeVariable("dimensionRatio", l.dimensionRatio); + writeVariable("barrierMargin", l.mBarrierMargin); + writeVariable("type", l.mHelperType); + writeVariable("ReferenceId", l.mReferenceIdString); + writeVariable("mBarrierAllowsGoneWidgets", + l.mBarrierAllowsGoneWidgets, true); + writeVariable("WrapBehavior", l.mWrapBehavior); + + writeVariable("verticalWeight", l.verticalWeight); + writeVariable("horizontalWeight", l.horizontalWeight); + writeVariable("horizontalChainStyle", l.horizontalChainStyle); + writeVariable("verticalChainStyle", l.verticalChainStyle); + writeVariable("barrierDirection", l.mBarrierDirection); + if (l.mReferenceIds != null) { + writeVariable("ReferenceIds", l.mReferenceIds); + } + writeTransform(c.transform); + writeCustom(c.mCustomConstraints); + + mWriter.write(" },\n"); + } + mWriter.write("},\n"); + } + + private void writeTransform(ConstraintSet.Transform transform) throws IOException { + if (transform.applyElevation) { + writeVariable("elevation", transform.elevation); + } + writeVariable("rotationX", transform.rotationX, 0); + writeVariable("rotationY", transform.rotationY, 0); + writeVariable("rotationZ", transform.rotation, 0); + writeVariable("scaleX", transform.scaleX, 1); + writeVariable("scaleY", transform.scaleY, 1); + writeVariable("translationX", transform.translationX, 0); + writeVariable("translationY", transform.translationY, 0); + writeVariable("translationZ", transform.translationZ, 0); + } + + private void writeCustom(HashMap cset) throws IOException { + if (cset != null && cset.size() > 0) { + mWriter.write(INDENT + "custom: {\n"); + for (String s : cset.keySet()) { + ConstraintAttribute attr = cset.get(s); + if (attr == null) { + continue; + } + String custom = INDENT + SMALL_INDENT + attr.getName() + ": "; + switch (attr.getType()) { + case INT_TYPE: + custom += attr.getIntegerValue(); + break; + case COLOR_TYPE: + custom += colorString(attr.getColorValue()); + break; + case FLOAT_TYPE: + custom += attr.getFloatValue(); + break; + case STRING_TYPE: + custom += "'" + attr.getStringValue() + "'"; + break; + case DIMENSION_TYPE: + custom = custom + attr.getFloatValue(); + break; + case REFERENCE_TYPE: + case COLOR_DRAWABLE_TYPE: + case BOOLEAN_TYPE: + custom = null; + } + if (custom != null) { + mWriter.write(custom + ",\n"); + } + } + mWriter.write(SMALL_INDENT + " } \n"); + } + } + + private static String colorString(int v) { + String str = "00000000" + Integer.toHexString(v); + return "#" + str.substring(str.length() - 8); + } + + private void writeGuideline(int orientation, + int guideBegin, + int guideEnd, + float guidePercent) throws IOException { + writeVariable("orientation", orientation); + writeVariable("guideBegin", guideBegin); + writeVariable("guideEnd", guideEnd); + writeVariable("guidePercent", guidePercent); + } + + private void writeDimension(String dimString, + int dim, + int dimDefault, + float dimPercent, + int dimMin, + int dimMax, + boolean unusedConstrainedDim) throws IOException { + if (dim == 0) { + if (dimMax != UNSET || dimMin != UNSET) { + String s = "-----"; + switch (dimDefault) { + case 0: // spread + s = INDENT + dimString + ": {value:'spread'"; + break; + case 1: // wrap + s = INDENT + dimString + ": {value:'wrap'"; + break; + case 2: // percent + s = INDENT + dimString + ": {value: '" + dimPercent + "%'"; + break; + } + if (dimMax != UNSET) { + s += ", max: " + dimMax; + } + if (dimMax != UNSET) { + s += ", min: " + dimMin; + } + s += "},\n"; + mWriter.write(s); + return; + } + + switch (dimDefault) { + case 0: // spread is the default + break; + case 1: // wrap + mWriter.write(INDENT + dimString + ": '???????????',\n"); + return; + case 2: // percent + mWriter.write(INDENT + dimString + ": '" + dimPercent + "%',\n"); + } + + } else if (dim == -2) { + mWriter.write(INDENT + dimString + ": 'wrap',\n"); + } else if (dim == -1) { + mWriter.write(INDENT + dimString + ": 'parent',\n"); + } else { + mWriter.write(INDENT + dimString + ": " + dim + ",\n"); + } + } + + private String getSimpleName(int id) { + if (mIdMap.containsKey(id)) { + return "" + mIdMap.get(id); + } + if (id == 0) { + return "parent"; + } + String name = lookup(id); + mIdMap.put(id, name); + return "" + name + ""; + } + + private String getName(int id) { + return "'" + getSimpleName(id) + "'"; + } + + private String lookup(int id) { + try { + if (mNames.containsKey(id)) { + return mNames.get(id); + } + if (id != -1) { + return mContext.getResources().getResourceEntryName(id); + } else { + return "unknown" + ++mUnknownCount; + } + } catch (Exception ex) { + return "unknown" + ++mUnknownCount; + } + } + + private void writeConstraint(String my, + int constraint, + String other, + int margin, + int goneMargin) throws IOException { + if (constraint == UNSET) { + return; + } + mWriter.write(INDENT + my); + mWriter.write(":["); + mWriter.write(getName(constraint)); + mWriter.write(", "); + mWriter.write("'" + other + "'"); + if (margin != 0 || goneMargin != UNSET_GONE_MARGIN) { + mWriter.write(", " + margin); + if (goneMargin != UNSET_GONE_MARGIN) { + mWriter.write(", " + goneMargin); + } + } + mWriter.write("],\n"); + } + + private void writeCircle(int circleConstraint, + float circleAngle, + int circleRadius) throws IOException { + if (circleConstraint == UNSET) { + return; + } + mWriter.write(INDENT + "circle"); + mWriter.write(":["); + mWriter.write(getName(circleConstraint)); + mWriter.write(", " + circleAngle); + mWriter.write(circleRadius + "],\n"); + } + + private void writeVariable(String name, int value) throws IOException { + if (value == 0 || value == -1) { + return; + } + mWriter.write(INDENT + name); + mWriter.write(": " + value); + mWriter.write(",\n"); + } + + private void writeVariable(String name, float value) throws IOException { + if (value == UNSET) { + return; + } + mWriter.write(INDENT + name); + mWriter.write(": " + value); + mWriter.write(",\n"); + } + + private void writeVariable(String name, float value, float def) throws IOException { + if (value == def) { + return; + } + mWriter.write(INDENT + name); + mWriter.write(": " + value); + mWriter.write(",\n"); + } + + private void writeVariable(String name, boolean value, boolean def) throws IOException { + if (value == def) { + return; + } + mWriter.write(INDENT + name); + mWriter.write(": " + value); + mWriter.write(",\n"); + } + + private void writeVariable(String name, int[] value) throws IOException { + if (value == null) { + return; + } + mWriter.write(INDENT + name); + mWriter.write(": "); + for (int i = 0; i < value.length; i++) { + mWriter.write(((i == 0) ? "[" : ", ") + getName(value[i])); + } + mWriter.write("],\n"); + } + + private void writeVariable(String name, String value) throws IOException { + if (value == null) { + return; + } + mWriter.write(INDENT + name); + mWriter.write(": '" + value); + mWriter.write("',\n"); + } + } +}