@reactvision/react-viro 2.44.0 → 2.44.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/android/react_viro/react_viro-release.aar +0 -0
  2. package/android/viro_renderer/viro_renderer-release.aar +0 -0
  3. package/components/AR/ViroARPlaneSelector.tsx +226 -100
  4. package/components/Material/ViroMaterials.ts +4 -2
  5. package/components/Types/ViroEvents.ts +50 -5
  6. package/components/Utilities/ViroVersion.ts +1 -1
  7. package/dist/components/AR/ViroARPlaneSelector.d.ts +15 -15
  8. package/dist/components/AR/ViroARPlaneSelector.js +130 -55
  9. package/dist/components/Material/ViroMaterials.js +2 -2
  10. package/dist/components/Types/ViroEvents.d.ts +30 -4
  11. package/dist/components/Utilities/ViroVersion.d.ts +1 -1
  12. package/dist/components/Utilities/ViroVersion.js +1 -1
  13. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
  14. package/ios/dist/ViroRenderer/ViroKit.framework/CardboardSDK.bundle/Info.plist +0 -0
  15. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARDeclarativeNode.h +1 -1
  16. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARDeclarativePlane.h +21 -2
  17. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARPlaneAnchor.h +191 -4
  18. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROVector2f.h +124 -0
  19. package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
  20. package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
  21. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
  22. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/CardboardSDK.bundle/Info.plist +0 -0
  23. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/Headers/VROARDeclarativeNode.h +1 -1
  24. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/Headers/VROARDeclarativePlane.h +21 -2
  25. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/Headers/VROARPlaneAnchor.h +191 -4
  26. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/Headers/VROVector2f.h +124 -0
  27. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/Info.plist +0 -0
  28. package/ios/dist/ViroRenderer/armv7_arm64/ViroKit.framework/ViroKit +0 -0
  29. package/ios/dist/ViroRenderer/x86_64/ViroKit.framework/CardboardSDK.bundle/Info.plist +0 -0
  30. package/ios/dist/ViroRenderer/x86_64/ViroKit.framework/Info.plist +0 -0
  31. package/ios/dist/armv7_arm64/libViroReact.a +0 -0
  32. package/ios/dist/lib/libViroReact.a +0 -0
  33. package/package.json +3 -3
@@ -48,8 +48,8 @@ const React = __importStar(require("react"));
48
48
  const ViroMaterials_1 = require("../Material/ViroMaterials");
49
49
  const ViroNode_1 = require("../ViroNode");
50
50
  const ViroQuad_1 = require("../ViroQuad");
51
+ const ViroPolygon_1 = require("../ViroPolygon");
51
52
  const ViroARPlane_1 = require("./ViroARPlane");
52
- var _maxPlanes = 15;
53
53
  var _planePrefix = "ViroARPlaneSelector_Plane_";
54
54
  /**
55
55
  * This component wraps the logic required to enable user selection
@@ -59,9 +59,8 @@ var _planePrefix = "ViroARPlaneSelector_Plane_";
59
59
  class ViroARPlaneSelector extends React.Component {
60
60
  _component = null;
61
61
  state = {
62
- selectedSurface: -1,
63
- foundARPlanes: [],
64
- arPlaneSizes: [],
62
+ selectedPlaneId: null,
63
+ foundARPlanes: new Map(),
65
64
  };
66
65
  render() {
67
66
  // Uncomment this line to check for misnamed props
@@ -69,78 +68,151 @@ class ViroARPlaneSelector extends React.Component {
69
68
  return <ViroNode_1.ViroNode>{this._getARPlanes()}</ViroNode_1.ViroNode>;
70
69
  }
71
70
  _getARPlanes() {
72
- // Always render a fixed number of planes, controlling visibility instead of conditional rendering
73
- let arPlanes = [];
74
- let numPlanes = this.props.maxPlanes || _maxPlanes;
75
- // Create all plane slots (both detected and placeholder)
76
- for (let i = 0; i < numPlanes; i++) {
77
- // Determine if this is the selected plane
78
- const isSelected = this.state.selectedSurface === i;
79
- // Get real plane data if available, or use defaults
80
- const foundARPlane = this.state.foundARPlanes[i];
81
- const hasPlaneData = !!foundARPlane;
82
- // Extract plane data or use defaults
83
- const surfaceWidth = hasPlaneData ? foundARPlane.width || 0.5 : 0.5;
84
- const surfaceHeight = hasPlaneData ? foundARPlane.height || 0.5 : 0.5;
85
- const surfacePosition = hasPlaneData
86
- ? foundARPlane.center || [0, 0, 0]
87
- : [0, 0, 0];
88
- const anchorId = hasPlaneData
89
- ? foundARPlane.anchorId
90
- : undefined;
91
- // Determine visibility based on selection state
92
- // - In selection mode (selectedSurface === -1): show all planes
93
- // - In selected mode: only show the selected plane
94
- const isVisible = this.state.selectedSurface === -1 || isSelected;
95
- arPlanes.push(<ViroARPlane_1.ViroARPlane key={_planePrefix + i} minWidth={this.props.minWidth || 0.5} minHeight={this.props.minHeight || 0.5} alignment={this.props.alignment} anchorId={anchorId} onAnchorFound={(anchor) => {
96
- // If we find an anchor, update our plane data
97
- this._onARPlaneUpdated(i)(anchor);
98
- }} onAnchorUpdated={this._onARPlaneUpdated(i)}>
99
- {/* Always render both the quad and children, controlling only visibility */}
100
- <ViroQuad_1.ViroQuad materials={"ViroARPlaneSelector_Translucent"} onClickState={(clickState, position, source) => this._getOnClickSurface(i, { clickState, position, source })} position={surfacePosition} width={surfaceWidth} height={surfaceHeight} rotation={[-90, 0, 0]} opacity={isSelected ? 0 : isVisible ? 1 : 0}/>
101
-
102
- {/* Wrap children in a ViroNode to control visibility if children exist */}
103
- {this.props.children && (<ViroNode_1.ViroNode opacity={isSelected ? 1 : 0}>
104
- {this.props.children}
105
- </ViroNode_1.ViroNode>)}
106
- </ViroARPlane_1.ViroARPlane>);
71
+ const arPlanes = [];
72
+ const detectBothAlignments = this.props.alignment === "Both" || !this.props.alignment;
73
+ // Determine which alignments to detect
74
+ const alignmentsToDetect = [];
75
+ if (detectBothAlignments) {
76
+ alignmentsToDetect.push("Horizontal", "Vertical");
107
77
  }
78
+ else if (this.props.alignment) {
79
+ // Type assertion safe here because we know it's not "Both" due to detectBothAlignments check
80
+ alignmentsToDetect.push(this.props.alignment);
81
+ }
82
+ // Create detector ViroARPlane components for each alignment type
83
+ // These don't have anchorId set initially, but will discover and track planes
84
+ // We add visual children based on detected plane data
85
+ const detectorsPerAlignment = 25; // 25 detectors per alignment type
86
+ alignmentsToDetect.forEach((alignment) => {
87
+ for (let i = 0; i < detectorsPerAlignment; i++) {
88
+ const detectorKey = `${_planePrefix}detector_${alignment}_${i}`;
89
+ // Check if this detector has discovered a plane
90
+ // We'll match by checking if any plane in foundARPlanes has this alignment
91
+ // and hasn't been assigned to a previous detector
92
+ // Note: ARCore returns "HorizontalUpward", "HorizontalDownward", etc.
93
+ // so we need to check if alignment starts with the requested type
94
+ const detectedPlanes = Array.from(this.state.foundARPlanes.entries()).filter(([_id, plane]) => {
95
+ if (alignment === "Horizontal") {
96
+ return plane.alignment.includes("Horizontal");
97
+ }
98
+ else if (alignment === "Vertical") {
99
+ return plane.alignment.includes("Vertical");
100
+ }
101
+ return plane.alignment === alignment;
102
+ });
103
+ const planeData = detectedPlanes[i]?.[1];
104
+ const anchorId = detectedPlanes[i]?.[0];
105
+ const hasPlaneData = !!planeData;
106
+ // Extract visual rendering data if plane detected
107
+ let visualElement = null;
108
+ if (hasPlaneData) {
109
+ const isSelected = this.state.selectedPlaneId === anchorId;
110
+ const surfaceWidth = planeData.width || 0.5;
111
+ const surfaceHeight = planeData.height || 0.5;
112
+ const vertices3D = planeData.vertices;
113
+ // Convert 3D vertices to 2D based on plane alignment
114
+ // ViroARPlane provides vertices in the plane's LOCAL coordinate system
115
+ // where the plane is always in the XZ plane. The anchor handles world orientation.
116
+ // Always extract [x, z] since vertices are in the plane's local XZ plane
117
+ const vertices2D = vertices3D && vertices3D.length >= 3
118
+ ? vertices3D.map(([x, _y, z]) => [
119
+ x,
120
+ z,
121
+ ])
122
+ : undefined;
123
+ // Rotation for ViroPolygon:
124
+ // ViroPolygon renders in XY plane by default, vertices are provided in XZ
125
+ // Need to rotate to map XZ plane to XY rendering plane
126
+ const polygonRotation = [-90, 0, 0];
127
+ const isVisible = this.state.selectedPlaneId === null || isSelected;
128
+ // Use actual plane shapes (ViroPolygon with vertices)
129
+ const forceQuadForAndroid = false; // Now using actual shapes on Android
130
+ const useActualShape = !forceQuadForAndroid &&
131
+ this.props.useActualShape !== false &&
132
+ vertices2D &&
133
+ vertices2D.length >= 3;
134
+ const finalOpacity = isSelected ? 0 : isVisible ? 1 : 0;
135
+ visualElement = useActualShape ? (<ViroPolygon_1.ViroPolygon key={`polygon-${anchorId}`} vertices={vertices2D} holes={[]} materials={["ViroARPlaneSelector_Translucent"]} {...(!this.props.disableClickSelection && {
136
+ onClickState: (clickState, position, source) => this._getOnClickSurface(anchorId, {
137
+ clickState,
138
+ position,
139
+ source,
140
+ }),
141
+ })} position={[0, 0, 0]} rotation={polygonRotation} opacity={finalOpacity}/>) : (<ViroQuad_1.ViroQuad key={`quad-${anchorId}`} materials={["ViroARPlaneSelector_Translucent"]} {...(!this.props.disableClickSelection && {
142
+ onClickState: (clickState, position, source) => this._getOnClickSurface(anchorId, {
143
+ clickState,
144
+ position,
145
+ source,
146
+ }),
147
+ })} position={[0, 0, 0]} width={surfaceWidth} height={surfaceHeight} rotation={polygonRotation} opacity={finalOpacity}/>);
148
+ }
149
+ arPlanes.push(<ViroARPlane_1.ViroARPlane key={detectorKey} minWidth={this.props.minWidth || 0} minHeight={this.props.minHeight || 0} alignment={alignment} anchorId={hasPlaneData ? anchorId : undefined} onAnchorFound={(anchor) => {
150
+ this._onARPlaneUpdated(anchor);
151
+ }} onAnchorUpdated={(anchor) => {
152
+ this._onARPlaneUpdated(anchor);
153
+ }}>
154
+ {visualElement}
155
+ {hasPlaneData && this.props.children && (<ViroNode_1.ViroNode opacity={this.state.selectedPlaneId === anchorId ? 1 : 0}>
156
+ {this.props.children}
157
+ </ViroNode_1.ViroNode>)}
158
+ </ViroARPlane_1.ViroARPlane>);
159
+ }
160
+ });
108
161
  return arPlanes;
109
162
  }
110
- _getOnClickSurface = (index, event) => {
163
+ _getOnClickSurface = (anchorId, event) => {
111
164
  if (event.clickState < 3) {
112
165
  return;
113
166
  }
114
167
  // Get the plane data before updating state to avoid race conditions
115
- const selectedPlane = this.state.foundARPlanes[index];
168
+ const selectedPlane = this.state.foundARPlanes.get(anchorId);
116
169
  if (!selectedPlane) {
117
170
  console.warn("ViroARPlaneSelector: Cannot select plane - plane data not found");
118
171
  return;
119
172
  }
120
173
  // Update state and call callback with the captured data
121
- this.setState({ selectedSurface: index }, () => {
174
+ this.setState({ selectedPlaneId: anchorId }, () => {
122
175
  this._onPlaneSelected(selectedPlane);
123
176
  });
124
177
  };
125
- _onARPlaneUpdated = (index) => {
126
- return (updateMap) => {
127
- let newPlanes = [...this.state.foundARPlanes];
128
- newPlanes[index] = updateMap;
129
- this.setState({
130
- foundARPlanes: newPlanes,
131
- arPlaneSizes: this.state.arPlaneSizes,
132
- });
178
+ _onARPlaneUpdated = (anchor) => {
179
+ if (!anchor.anchorId) {
180
+ console.warn("ViroARPlaneSelector: Anchor missing anchorId");
181
+ return;
182
+ }
183
+ const updateMap = {
184
+ anchorId: anchor.anchorId,
185
+ type: anchor.type || "plane",
186
+ position: anchor.position,
187
+ rotation: anchor.rotation,
188
+ scale: anchor.scale,
189
+ center: anchor.center,
190
+ width: anchor.width,
191
+ height: anchor.height,
192
+ alignment: anchor.alignment,
193
+ classification: anchor.classification,
194
+ vertices: anchor.vertices,
133
195
  };
196
+ // Update or add plane in Map
197
+ this.setState((prevState) => {
198
+ const newPlanes = new Map(prevState.foundARPlanes);
199
+ newPlanes.set(anchor.anchorId, updateMap);
200
+ return { foundARPlanes: newPlanes };
201
+ });
202
+ // Call validation callback if provided
203
+ if (this.props.onPlaneDetected) {
204
+ this.props.onPlaneDetected(updateMap);
205
+ }
134
206
  };
135
207
  _onPlaneSelected = (updateMap) => {
136
208
  this.props.onPlaneSelected && this.props.onPlaneSelected(updateMap);
137
209
  };
138
- /*
139
- This function allows the user to reset the surface and select a new plane.
140
- */
210
+ /**
211
+ * This function allows the user to reset the surface and select a new plane.
212
+ */
141
213
  reset = () => {
142
214
  this.setState({
143
- selectedSurface: -1,
215
+ selectedPlaneId: null,
144
216
  });
145
217
  };
146
218
  }
@@ -148,6 +220,9 @@ exports.ViroARPlaneSelector = ViroARPlaneSelector;
148
220
  ViroMaterials_1.ViroMaterials.createMaterials({
149
221
  ViroARPlaneSelector_Translucent: {
150
222
  lightingModel: "Constant",
151
- diffuseColor: "#88888888",
223
+ diffuseColor: "rgba(0, 122, 255, 0.5)", // Bright blue with 50% opacity for better visibility
224
+ blendMode: "Alpha",
225
+ cullMode: "None", // Render both sides for better Android compatibility
226
+ writesToDepthBuffer: false,
152
227
  },
153
228
  });
@@ -17,7 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  exports.ViroMaterials = void 0;
18
18
  const react_native_1 = require("react-native");
19
19
  // @ts-ignore
20
- const AssetRegistry_1 = __importDefault(require("react-native/Libraries/Image/AssetRegistry"));
20
+ const AssetRegistry_1 = require("react-native/Libraries/Image/AssetRegistry");
21
21
  // @ts-ignore
22
22
  const resolveAssetSource_1 = __importDefault(require("react-native/Libraries/Image/resolveAssetSource"));
23
23
  var MaterialManager = react_native_1.NativeModules.VRTMaterialManager;
@@ -47,7 +47,7 @@ class ViroMaterials {
47
47
  else {
48
48
  var assetType = "unknown";
49
49
  if (typeof material[prop] !== "object") {
50
- var asset = AssetRegistry_1.default.getAssetByID(material[prop]);
50
+ var asset = (0, AssetRegistry_1.getAssetByID)(material[prop]);
51
51
  if (asset) {
52
52
  assetType = asset.type;
53
53
  }
@@ -167,6 +167,35 @@ export type ViroAnimatedComponentFinishEvent = {};
167
167
  /** ===========================================================================
168
168
  * Viro AR Anchor Events
169
169
  * ============================================================================ */
170
+ /**
171
+ * Classification of detected planes.
172
+ * iOS 12+ provides ML-based classification via ARKit.
173
+ * Android provides basic inference from plane orientation.
174
+ */
175
+ export type ViroARPlaneClassification = "None" | "Wall" | "Floor" | "Ceiling" | "Table" | "Seat" | "Door" | "Window" | "Unknown";
176
+ /**
177
+ * Alignment of detected planes with respect to gravity.
178
+ */
179
+ export type ViroARPlaneAlignment = "Horizontal" | "HorizontalUpward" | "HorizontalDownward" | "Vertical";
180
+ /**
181
+ * Represents an AR anchor detected in the real world.
182
+ */
183
+ export type ViroAnchor = {
184
+ anchorId: string;
185
+ type: "anchor" | "plane" | "image";
186
+ position: [number, number, number];
187
+ rotation: [number, number, number];
188
+ scale: [number, number, number];
189
+ center?: [number, number, number];
190
+ width?: number;
191
+ height?: number;
192
+ alignment?: ViroARPlaneAlignment;
193
+ classification?: ViroARPlaneClassification;
194
+ vertices?: Array<[number, number, number]>;
195
+ trackingMethod?: string;
196
+ };
197
+ export type ViroAnchorFoundMap = ViroAnchor;
198
+ export type ViroAnchorUpdatedMap = ViroAnchor;
170
199
  export type ViroARAnchorRemovedEvent = {
171
200
  anchor: ViroAnchor;
172
201
  };
@@ -178,13 +207,10 @@ export type ViroARAnchorFoundEvent = {
178
207
  anchorFoundMap: ViroAnchorFoundMap;
179
208
  anchor: ViroAnchor;
180
209
  };
181
- export type ViroAnchor = any;
182
- export type ViroAnchorFoundMap = any;
183
- export type ViroAnchorUpdatedMap = any;
184
210
  /** ===========================================================================
185
211
  * Viro AR Plane Events
186
212
  * ============================================================================ */
187
- export type ViroPlaneUpdatedMap = any;
213
+ export type ViroPlaneUpdatedMap = ViroAnchor;
188
214
  export type ViroPlaneUpdatedEvent = any;
189
215
  export type ViroARPlaneSizes = any;
190
216
  /** ===========================================================================
@@ -1 +1 @@
1
- export declare const VIRO_VERSION = "2.44.0";
1
+ export declare const VIRO_VERSION = "2.44.2";
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.VIRO_VERSION = void 0;
4
- exports.VIRO_VERSION = "2.44.0";
4
+ exports.VIRO_VERSION = "2.44.2";
@@ -101,7 +101,7 @@ public:
101
101
  delegate->onARAnchorAttached(getAnchor());
102
102
  }
103
103
  }
104
-
104
+
105
105
  virtual void onARAnchorUpdated() {
106
106
  std::shared_ptr<VROARDeclarativeNodeDelegate> delegate = getARNodeDelegate();
107
107
  if (delegate) {
@@ -46,14 +46,31 @@ public:
46
46
 
47
47
  /*
48
48
  Returns whether or not the given VROARAnchor fulfills this plane's requirements.
49
+
50
+ NOTE: This uses hysteresis for CONSTRAINT MATCHING only - it doesn't affect the
51
+ precision of plane data from ARCore/ARKit. Hysteresis prevents nodes from rapidly
52
+ attaching/detaching when plane dimensions fluctuate near the app's minimum requirements.
53
+
54
+ This is application-level logic and does not filter or modify ARCore/ARKit plane data.
49
55
  */
50
56
  bool hasRequirementsFulfilled(std::shared_ptr<VROARAnchor> candidate) {
51
57
  std::shared_ptr<VROARPlaneAnchor> planeAnchor = std::dynamic_pointer_cast<VROARPlaneAnchor>(candidate->getAnchorForTrackable());
52
58
  if (!planeAnchor) {
53
59
  return false;
54
60
  }
55
-
56
- if (planeAnchor->getExtent().x < _minWidth || planeAnchor->getExtent().z < _minHeight) {
61
+
62
+ // Apply hysteresis: if the plane is already attached to this anchor,
63
+ // allow it to be 10% smaller before detaching. This prevents flickering
64
+ // when plane dimensions fluctuate near the application's size threshold.
65
+ // This does NOT affect the precision of the plane data itself.
66
+ bool isCurrentlyAttached = (getAnchor() == candidate);
67
+ float hysteresisMargin = isCurrentlyAttached ? 0.90f : 1.0f;
68
+
69
+ float effectiveMinWidth = _minWidth * hysteresisMargin;
70
+ float effectiveMinHeight = _minHeight * hysteresisMargin;
71
+
72
+ if (planeAnchor->getExtent().x < effectiveMinWidth ||
73
+ planeAnchor->getExtent().z < effectiveMinHeight) {
57
74
  return false;
58
75
  }
59
76
 
@@ -98,10 +115,12 @@ public:
98
115
  && _alignment != VROARPlaneAlignment::HorizontalDownward) {
99
116
  return false;
100
117
  }
118
+ break;
101
119
  case VROARPlaneAlignment::Vertical:
102
120
  if (_alignment != VROARPlaneAlignment::Vertical) {
103
121
  return false;
104
122
  }
123
+ break;
105
124
  default:
106
125
  break;
107
126
  }
@@ -29,6 +29,20 @@
29
29
 
30
30
  #include "VROARAnchor.h"
31
31
  #include "VROVector3f.h"
32
+ #include "VROVector2f.h"
33
+ #include <chrono>
34
+
35
+ /*
36
+ ENABLED: Change detection and update throttling to reduce noise and artifacts.
37
+ This filters out small plane changes and prevents excessive updates, which is
38
+ particularly important for vertical plane detection where ARCore can be noisy.
39
+
40
+ Thresholds:
41
+ - Minimum extent change: 1cm or 5%
42
+ - Minimum center change: 1cm
43
+ - Update throttle: 100ms (10 updates/sec max)
44
+ */
45
+ #define VRO_PLANE_CHANGE_DETECTION_ENABLED
32
46
 
33
47
  enum class VROARPlaneAlignment {
34
48
  Horizontal = 0x1,
@@ -37,14 +51,33 @@ enum class VROARPlaneAlignment {
37
51
  Vertical = 0x10,
38
52
  };
39
53
 
54
+ /*
55
+ Classification of detected planes (iOS 12+, ARCore semantic labels).
56
+ Indicates the semantic meaning of a detected plane.
57
+ */
58
+ enum class VROARPlaneClassification {
59
+ None,
60
+ Wall,
61
+ Floor,
62
+ Ceiling,
63
+ Table,
64
+ Seat,
65
+ Door,
66
+ Window,
67
+ Unknown
68
+ };
69
+
40
70
  /*
41
71
  Anchor representing a planar surface.
42
72
  */
43
73
  class VROARPlaneAnchor : public VROARAnchor {
44
-
74
+
45
75
  public:
46
-
47
- VROARPlaneAnchor() {}
76
+
77
+ VROARPlaneAnchor() :
78
+ _lastUpdateTime(std::chrono::steady_clock::now()),
79
+ _updateCount(0),
80
+ _significantChangeCount(0) {}
48
81
  virtual ~VROARPlaneAnchor() {}
49
82
 
50
83
  /*
@@ -85,7 +118,140 @@ public:
85
118
  std::vector<VROVector3f> getBoundaryVertices() {
86
119
  return _boundaryVertices;
87
120
  }
88
-
121
+
122
+ /*
123
+ Full mesh geometry (iOS 11.3+ only - ARSCNPlaneGeometry equivalent).
124
+ Provides detailed tessellated surface representation beyond just boundary.
125
+ On Android/ARCore, these will be empty as ARCore only provides boundaries.
126
+ */
127
+ void setMeshVertices(std::vector<VROVector3f> vertices) {
128
+ _meshVertices = std::move(vertices);
129
+ }
130
+ std::vector<VROVector3f> getMeshVertices() const {
131
+ return _meshVertices;
132
+ }
133
+
134
+ void setTextureCoordinates(std::vector<VROVector2f> uvs) {
135
+ _textureCoordinates = std::move(uvs);
136
+ }
137
+ std::vector<VROVector2f> getTextureCoordinates() const {
138
+ return _textureCoordinates;
139
+ }
140
+
141
+ void setTriangleIndices(std::vector<int> indices) {
142
+ _triangleIndices = std::move(indices);
143
+ }
144
+ std::vector<int> getTriangleIndices() const {
145
+ return _triangleIndices;
146
+ }
147
+
148
+ /*
149
+ Plane classification (iOS 12+, ARCore semantic labels).
150
+ Indicates what type of surface this plane represents.
151
+ */
152
+ void setClassification(VROARPlaneClassification classification) {
153
+ _classification = classification;
154
+ }
155
+ VROARPlaneClassification getClassification() const {
156
+ return _classification;
157
+ }
158
+
159
+ /*
160
+ Change detection and throttling for plane updates.
161
+ */
162
+
163
+ // Check if plane properties have changed significantly
164
+ bool hasSignificantChanges(VROVector3f newCenter, VROVector3f newExtent,
165
+ VROARPlaneAlignment newAlignment,
166
+ const std::vector<VROVector3f> &newBoundaryVertices) const {
167
+ // Thresholds for detecting significant changes
168
+ static const float EXTENT_THRESHOLD = 0.01f; // 1cm change in dimensions
169
+ static const float CENTER_THRESHOLD = 0.01f; // 1cm change in center
170
+ static const float EXTENT_PERCENT_THRESHOLD = 0.05f; // 5% change in size
171
+
172
+ // Check alignment change
173
+ if (newAlignment != _alignment) {
174
+ return true;
175
+ }
176
+
177
+ // Check extent change (absolute and percentage)
178
+ VROVector3f extentDiff = newExtent - _extent;
179
+ float maxExtentDiff = std::max(std::abs(extentDiff.x), std::abs(extentDiff.z));
180
+ if (maxExtentDiff > EXTENT_THRESHOLD) {
181
+ // Also check percentage change for larger planes
182
+ if (_extent.magnitude() > 0.001f) {
183
+ float percentChange = maxExtentDiff / _extent.magnitude();
184
+ if (percentChange > EXTENT_PERCENT_THRESHOLD) {
185
+ return true;
186
+ }
187
+ } else {
188
+ // For very small planes, any change is significant
189
+ return true;
190
+ }
191
+ }
192
+
193
+ // Check center change
194
+ VROVector3f centerDiff = newCenter - _center;
195
+ if (centerDiff.magnitude() > CENTER_THRESHOLD) {
196
+ return true;
197
+ }
198
+
199
+ // Check boundary vertices count change
200
+ if (newBoundaryVertices.size() != _boundaryVertices.size()) {
201
+ return true;
202
+ }
203
+
204
+ // Check boundary vertices significant changes (sample-based for performance)
205
+ if (!newBoundaryVertices.empty() && !_boundaryVertices.empty()) {
206
+ // Sample a few vertices instead of checking all for performance
207
+ size_t sampleCount = std::min(size_t(4), newBoundaryVertices.size());
208
+ size_t step = newBoundaryVertices.size() / sampleCount;
209
+ if (step < 1) step = 1;
210
+
211
+ for (size_t i = 0; i < newBoundaryVertices.size(); i += step) {
212
+ VROVector3f diff = newBoundaryVertices[i] - _boundaryVertices[i];
213
+ if (diff.magnitude() > CENTER_THRESHOLD) {
214
+ return true;
215
+ }
216
+ }
217
+ }
218
+
219
+ return false;
220
+ }
221
+
222
+ // Check if update should be throttled
223
+ bool shouldThrottleUpdate() const {
224
+ // Minimum time between updates (milliseconds)
225
+ static const int MIN_UPDATE_INTERVAL_MS = 100; // 10 updates per second max
226
+
227
+ auto now = std::chrono::steady_clock::now();
228
+ auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastUpdateTime).count();
229
+
230
+ return elapsed < MIN_UPDATE_INTERVAL_MS;
231
+ }
232
+
233
+ // Mark that an update occurred
234
+ void recordUpdate(bool wasSignificant) {
235
+ _lastUpdateTime = std::chrono::steady_clock::now();
236
+ _updateCount++;
237
+ if (wasSignificant) {
238
+ _significantChangeCount++;
239
+ }
240
+ }
241
+
242
+ // Get update statistics for diagnostics
243
+ uint32_t getUpdateCount() const { return _updateCount; }
244
+ uint32_t getSignificantChangeCount() const { return _significantChangeCount; }
245
+ float getSignificantChangeRatio() const {
246
+ return _updateCount > 0 ? (float)_significantChangeCount / _updateCount : 0.0f;
247
+ }
248
+
249
+ // Get time since last update (milliseconds) - useful for debugging update frequency
250
+ int64_t getTimeSinceLastUpdate() const {
251
+ auto now = std::chrono::steady_clock::now();
252
+ return std::chrono::duration_cast<std::chrono::milliseconds>(now - _lastUpdateTime).count();
253
+ }
254
+
89
255
  private:
90
256
 
91
257
  /*
@@ -108,6 +274,27 @@ private:
108
274
  A vector of points representing the vertex boundaries of this plane, if any.
109
275
  */
110
276
  std::vector<VROVector3f> _boundaryVertices;
277
+
278
+ /*
279
+ Full mesh geometry (iOS 11.3+ only).
280
+ Detailed tessellated mesh from ARSCNPlaneGeometry.
281
+ Empty on Android as ARCore only provides boundary polygon.
282
+ */
283
+ std::vector<VROVector3f> _meshVertices;
284
+ std::vector<VROVector2f> _textureCoordinates;
285
+ std::vector<int> _triangleIndices;
286
+
287
+ /*
288
+ Plane classification (iOS 12+, ARCore semantic labels).
289
+ */
290
+ VROARPlaneClassification _classification = VROARPlaneClassification::None;
291
+
292
+ /*
293
+ Update tracking and throttling.
294
+ */
295
+ std::chrono::steady_clock::time_point _lastUpdateTime;
296
+ uint32_t _updateCount;
297
+ uint32_t _significantChangeCount;
111
298
  };
112
299
 
113
300
  #endif /* VROARPlaneAnchor_h */