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
+
+[](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");
+ }
+ }
+}