1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package androidx.xr.compose.spatial
18
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.remember
21 import androidx.compose.ui.geometry.Offset
22 import androidx.compose.ui.platform.LocalDensity
23 import androidx.compose.ui.unit.Density
24 import androidx.compose.ui.unit.Dp
25 import androidx.compose.ui.unit.IntSize
26 import androidx.compose.ui.unit.dp
27 import androidx.xr.compose.unit.Meter
28 import androidx.xr.compose.unit.toMeter
29 import androidx.xr.runtime.math.Pose
30
31 /** Calculate a [Pose] in 3D space based on the relative offset within the 2D space of a Panel. */
32 @Composable
rememberCalculatePosenull33 internal fun rememberCalculatePose(
34 contentOffset: Offset,
35 parentViewSize: IntSize,
36 contentSize: IntSize,
37 zDepth: Dp = 0.dp,
38 ): Pose {
39 val density = LocalDensity.current
40 return remember(contentOffset, parentViewSize, contentSize, zDepth) {
41 calculatePose(contentOffset, parentViewSize, contentSize, density, zDepth)
42 }
43 }
44
calculatePosenull45 internal fun calculatePose(
46 contentOffset: Offset,
47 parentViewSize: IntSize,
48 contentSize: IntSize,
49 density: Density,
50 zDepth: Dp = 0.dp,
51 ): Pose {
52 val meterPosition =
53 contentOffset.toMeterPosition(parentViewSize, contentSize, density) +
54 MeterPosition(z = zDepth.toMeter())
55 return Pose(translation = meterPosition.toVector3())
56 }
57
58 /**
59 * Resolves the coordinate systems between 2D app pixel space and 3D meter space. In 2d space, views
60 * and composables are anchored at the top left corner; however, in 3D space they are anchored at
61 * the center. This fixes that by adjusting for the space size and the content's size so they are
62 * anchored in the top left corner in 3D space.
63 *
64 * This conversion requires that [density] be specified.
65 */
toMeterPositionnull66 private fun Offset.toMeterPosition(
67 parentViewSize: IntSize,
68 contentSize: IntSize,
69 density: Density,
70 ) =
71 MeterPosition(
72 Meter.fromPixel(x.scale(contentSize.width, parentViewSize.width), density),
73 Meter.fromPixel(-y.scale(contentSize.height, parentViewSize.height), density),
74 )
75
76 private fun Float.scale(size: Int, space: Int) = this + (size - space) / 2.0f
77