improves placement logic and implements a feature to disable touch interaction in ar mode
parent
c6f789a7c9
commit
8b07a88309
File diff suppressed because one or more lines are too long
|
|
@ -68,6 +68,7 @@ const $triggerLoad = Symbol('triggerLoad');
|
||||||
export declare interface ARInterface {
|
export declare interface ARInterface {
|
||||||
ar: boolean;
|
ar: boolean;
|
||||||
arModes: string;
|
arModes: string;
|
||||||
|
arInteraction: boolean;
|
||||||
arScale: string;
|
arScale: string;
|
||||||
arPlacement: 'floor'|'wall'|'ceiling';
|
arPlacement: 'floor'|'wall'|'ceiling';
|
||||||
arAnchor: string|null;
|
arAnchor: string|null;
|
||||||
|
|
@ -84,6 +85,9 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
|
||||||
class ARModelViewerElement extends ModelViewerElement {
|
class ARModelViewerElement extends ModelViewerElement {
|
||||||
@property({type: Boolean, attribute: 'ar'}) ar: boolean = false;
|
@property({type: Boolean, attribute: 'ar'}) ar: boolean = false;
|
||||||
|
|
||||||
|
@property({type: Boolean, attribute: 'ar-interaction'})
|
||||||
|
arInteraction: boolean = true;
|
||||||
|
|
||||||
@property({type: String, attribute: 'ar-scale'}) arScale: string = 'auto';
|
@property({type: String, attribute: 'ar-scale'}) arScale: string = 'auto';
|
||||||
|
|
||||||
@property({type: String, attribute: 'ar-usdz-max-texture-size'})
|
@property({type: String, attribute: 'ar-usdz-max-texture-size'})
|
||||||
|
|
@ -181,6 +185,10 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
|
||||||
this[$scene].canScale = this.arScale !== 'fixed';
|
this[$scene].canScale = this.arScale !== 'fixed';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('arInteraction')) {
|
||||||
|
this[$renderer].arRenderer.isInteractionEnabled = this.arInteraction;
|
||||||
|
}
|
||||||
|
|
||||||
if (changedProperties.has('arPlacement')) {
|
if (changedProperties.has('arPlacement')) {
|
||||||
this[$scene].updateShadow();
|
this[$scene].updateShadow();
|
||||||
this[$needsRender]();
|
this[$needsRender]();
|
||||||
|
|
@ -194,6 +202,17 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
|
||||||
this[$arModes] = deserializeARModes(this.arModes);
|
this[$arModes] = deserializeARModes(this.arModes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changedProperties.has('arAnchor') && this[$renderer].arRenderer.isPresenting) {
|
||||||
|
const arRenderer = this[$renderer].arRenderer;
|
||||||
|
const isDeferredCeiling = this.arPlacement === 'ceiling' &&
|
||||||
|
!arRenderer.isObjectPlaced &&
|
||||||
|
!arRenderer.presentedScene?.visible;
|
||||||
|
|
||||||
|
if (!isDeferredCeiling) {
|
||||||
|
arRenderer.updateAnchor(this.arAnchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (changedProperties.has('ar') || changedProperties.has('arModes') ||
|
if (changedProperties.has('ar') || changedProperties.has('arModes') ||
|
||||||
changedProperties.has('src') || changedProperties.has('iosSrc') ||
|
changedProperties.has('src') || changedProperties.has('iosSrc') ||
|
||||||
changedProperties.has('arUsdzMaxTextureSize')) {
|
changedProperties.has('arUsdzMaxTextureSize')) {
|
||||||
|
|
@ -204,7 +223,7 @@ export const ARMixin = <T extends Constructor<ModelViewerElementBase>>(
|
||||||
public getAnchor(): string {
|
public getAnchor(): string {
|
||||||
const arRenderer = this[$renderer].arRenderer;
|
const arRenderer = this[$renderer].arRenderer;
|
||||||
if (arRenderer.isPresenting && arRenderer.isObjectPlaced) {
|
if (arRenderer.isPresenting && arRenderer.isObjectPlaced) {
|
||||||
const position = arRenderer.currentGoalPosition;
|
const position = arRenderer.currentPosition;
|
||||||
return `${position.x} ${position.y} ${position.z}`;
|
return `${position.x} ${position.y} ${position.z}`;
|
||||||
}
|
}
|
||||||
return 'Model not placed in AR yet.';
|
return 'Model not placed in AR yet.';
|
||||||
|
|
@ -297,6 +316,7 @@ configuration or device capabilities');
|
||||||
this[$arButtonContainer].removeEventListener(
|
this[$arButtonContainer].removeEventListener(
|
||||||
'click', this[$onARButtonContainerClick]);
|
'click', this[$onARButtonContainerClick]);
|
||||||
const {arRenderer} = this[$renderer];
|
const {arRenderer} = this[$renderer];
|
||||||
|
arRenderer.isInteractionEnabled = this.arInteraction;
|
||||||
if (this.arPlacement === 'wall') {
|
if (this.arPlacement === 'wall') {
|
||||||
arRenderer.placementMode = 'wall';
|
arRenderer.placementMode = 'wall';
|
||||||
} else if (this.arPlacement === 'ceiling') {
|
} else if (this.arPlacement === 'ceiling') {
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,7 @@ export class ARRenderer extends EventDispatcher<
|
||||||
public currentSession: XRSession|null = null;
|
public currentSession: XRSession|null = null;
|
||||||
public placementMode:'floor'|'wall'|'ceiling' = 'floor';
|
public placementMode:'floor'|'wall'|'ceiling' = 'floor';
|
||||||
public anchorOffset: string|null = null;
|
public anchorOffset: string|null = null;
|
||||||
|
public isInteractionEnabled = true;
|
||||||
|
|
||||||
private placementBox: PlacementBox|null = null;
|
private placementBox: PlacementBox|null = null;
|
||||||
private menuPanel: XRMenuPanel|null = null;
|
private menuPanel: XRMenuPanel|null = null;
|
||||||
|
|
@ -230,6 +231,13 @@ export class ARRenderer extends EventDispatcher<
|
||||||
return this.goalPosition;
|
return this.goalPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get currentPosition(): Vector3 {
|
||||||
|
if (this.presentedScene) {
|
||||||
|
return this.presentedScene.pivot.position;
|
||||||
|
}
|
||||||
|
return this.goalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
public get isObjectPlaced(): boolean {
|
public get isObjectPlaced(): boolean {
|
||||||
return this.placementComplete;
|
return this.placementComplete;
|
||||||
}
|
}
|
||||||
|
|
@ -417,8 +425,10 @@ export class ARRenderer extends EventDispatcher<
|
||||||
|
|
||||||
private setupController(controller: XRController) {
|
private setupController(controller: XRController) {
|
||||||
this.setupXRControllerLine(controller);
|
this.setupXRControllerLine(controller);
|
||||||
controller.addEventListener('selectstart', this.onControllerSelectStart);
|
if (this.isInteractionEnabled) {
|
||||||
controller.addEventListener('selectend', this.onControllerSelectEnd);
|
controller.addEventListener('selectstart', this.onControllerSelectStart);
|
||||||
|
controller.addEventListener('selectend', this.onControllerSelectEnd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private setupXRControllers() {
|
private setupXRControllers() {
|
||||||
this.xrController1 = this.threeRenderer.xr.getController(0) as XRController;
|
this.xrController1 = this.threeRenderer.xr.getController(0) as XRController;
|
||||||
|
|
@ -801,61 +811,52 @@ export class ARRenderer extends EventDispatcher<
|
||||||
|
|
||||||
if (this.parsedAnchorOffset != null) {
|
if (this.parsedAnchorOffset != null) {
|
||||||
// Set position directly from the provided anchor offset.
|
// Set position directly from the provided anchor offset.
|
||||||
// This position is relative to the initial XR reference space origin.
|
|
||||||
position.copy(this.parsedAnchorOffset);
|
position.copy(this.parsedAnchorOffset);
|
||||||
this.goalPosition.copy(this.parsedAnchorOffset);
|
this.goalPosition.copy(this.parsedAnchorOffset);
|
||||||
|
|
||||||
// Set up the scene for presentation
|
this.placementComplete = true;
|
||||||
|
this.worldSpaceInitialPlacementDone = true;
|
||||||
scene.setHotspotsVisibility(true);
|
scene.setHotspotsVisibility(true);
|
||||||
scene.visible = true;
|
scene.visible = true;
|
||||||
scene.setShadowIntensity(AR_SHADOW_INTENSITY);
|
scene.setShadowIntensity(AR_SHADOW_INTENSITY);
|
||||||
|
|
||||||
// Mark placement as complete to bypass further automatic placement.
|
|
||||||
this.placementComplete = true;
|
|
||||||
this.worldSpaceInitialPlacementDone = true;
|
|
||||||
|
|
||||||
// Hide the placement box as it's not needed.
|
|
||||||
if (this.placementBox) {
|
if (this.placementBox) {
|
||||||
this.placementBox.show = false;
|
this.placementBox.show = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
||||||
|
|
||||||
// Enable user interaction controls for the appropriate mode.
|
if (this.isInteractionEnabled) {
|
||||||
if (this.xrMode === XRMode.SCREEN_SPACE) {
|
if (this.xrMode === XRMode.SCREEN_SPACE) {
|
||||||
const {session} = this.frame!;
|
const { session } = this.frame!;
|
||||||
session.addEventListener('selectstart', this.onSelectStart);
|
session.addEventListener('selectstart', this.onSelectStart);
|
||||||
session.addEventListener('selectend', this.onSelectEnd);
|
session.addEventListener('selectend', this.onSelectEnd);
|
||||||
session.requestHitTestSourceForTransientInput!({profile: 'generic-touchscreen'})!
|
session.requestHitTestSourceForTransientInput!({ profile: 'generic-touchscreen' })!
|
||||||
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
||||||
} else { // WORLD_SPACE
|
} else { // WORLD_SPACE
|
||||||
this.enableWorldSpaceUserInteraction();
|
this.enableWorldSpaceUserInteraction();
|
||||||
// Hide the placement box again as it's not needed for manual anchoring.
|
|
||||||
if (this.placementBox) {
|
|
||||||
this.placementBox.show = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return; // Skip the rest of the automatic placement logic.
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {width, height} = this.overlay!.getBoundingClientRect();
|
const {width, height} = this.overlay!.getBoundingClientRect();
|
||||||
scene.setSize(width, height);
|
scene.setSize(width, height);
|
||||||
|
|
||||||
xrCamera.projectionMatrixInverse.copy(xrCamera.projectionMatrix).invert();
|
xrCamera.projectionMatrixInverse.copy(xrCamera.projectionMatrix).invert();
|
||||||
|
|
||||||
const {theta} = (element as ModelViewerElementBase & ControlsInterface)
|
const {theta} = (element as ModelViewerElementBase & ControlsInterface)
|
||||||
.getCameraOrbit();
|
.getCameraOrbit();
|
||||||
|
|
||||||
// Orient model to match the 3D camera view
|
// Orient model to match the 3D camera view
|
||||||
const cameraDirection = xrCamera.getWorldDirection(vector3);
|
const cameraDirection = xrCamera.getWorldDirection(vector3);
|
||||||
scene.yaw = Math.atan2(-cameraDirection.x, -cameraDirection.z) - theta;
|
scene.yaw = Math.atan2(-cameraDirection.x, -cameraDirection.z) - theta;
|
||||||
this.goalYaw = scene.yaw;
|
this.goalYaw = scene.yaw;
|
||||||
|
|
||||||
if (this.placeOnCeiling && !this.isViewPointingUp()) {
|
if (this.placeOnCeiling && !this.isViewPointingUp()) {
|
||||||
scene.visible = false; // Hide until properly oriented
|
scene.visible = false;
|
||||||
scene.setHotspotsVisibility(true); // Still show UI
|
scene.setHotspotsVisibility(true);
|
||||||
|
|
||||||
// Set up touch interaction for screen-space mode
|
|
||||||
if (this.xrMode === XRMode.SCREEN_SPACE) {
|
if (this.xrMode === XRMode.SCREEN_SPACE) {
|
||||||
const {session} = this.frame!;
|
const {session} = this.frame!;
|
||||||
session.addEventListener('selectstart', this.onSelectStart);
|
session.addEventListener('selectstart', this.onSelectStart);
|
||||||
|
|
@ -863,58 +864,58 @@ export class ARRenderer extends EventDispatcher<
|
||||||
session.requestHitTestSourceForTransientInput!({profile: 'generic-touchscreen'})!
|
session.requestHitTestSourceForTransientInput!({profile: 'generic-touchscreen'})!
|
||||||
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
||||||
}
|
}
|
||||||
return; // Exit early - don't place yet
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use different placement logic for world-space vs screen-space
|
if (this.xrMode === XRMode.WORLD_SPACE) {
|
||||||
if (this.xrMode === XRMode.WORLD_SPACE && !this.worldSpaceInitialPlacementDone) {
|
const {position: optimalPosition, scale: optimalScale} =
|
||||||
// Use automatic optimal placement for world-space AR only on first session
|
this.calculateWorldSpaceOptimalPlacement(scene, xrCamera);
|
||||||
const {position: optimalPosition, scale: optimalScale} =
|
|
||||||
this.calculateWorldSpaceOptimalPlacement(scene, xrCamera);
|
this.goalPosition.copy(optimalPosition);
|
||||||
|
this.goalScale = optimalScale;
|
||||||
this.goalPosition.copy(optimalPosition);
|
this.initialModelScale = optimalScale;
|
||||||
this.goalScale = optimalScale;
|
|
||||||
|
position.copy(optimalPosition);
|
||||||
// Store the initial scale for toggle functionality
|
pivot.scale.set(optimalScale, optimalScale, optimalScale);
|
||||||
this.initialModelScale = optimalScale;
|
|
||||||
|
this.worldSpaceInitialPlacementDone = true;
|
||||||
// Set initial position and scale immediately for world-space
|
this.calculateWorldSpaceScaleLimits(scene);
|
||||||
position.copy(optimalPosition);
|
this.enableWorldSpaceUserInteraction();
|
||||||
pivot.scale.set(optimalScale, optimalScale, optimalScale);
|
|
||||||
|
} else { // SCREEN_SPACE
|
||||||
// Mark that initial placement is done
|
const radius = Math.max(1, 2 * scene.boundingSphere.radius);
|
||||||
this.worldSpaceInitialPlacementDone = true;
|
position.copy(xrCamera.position)
|
||||||
|
.add(cameraDirection.multiplyScalar(radius));
|
||||||
// Calculate scale limits for world-space mode (SVXR logic)
|
|
||||||
this.calculateWorldSpaceScaleLimits(scene);
|
this.updateTarget();
|
||||||
|
const target = scene.getTarget();
|
||||||
// Enable user interaction after initial placement
|
position.add(target).sub(this.oldTarget);
|
||||||
this.enableWorldSpaceUserInteraction();
|
|
||||||
} else if (this.xrMode === XRMode.SCREEN_SPACE) {
|
this.goalPosition.copy(position);
|
||||||
// Use original placement logic for screen-space AR
|
|
||||||
const radius = Math.max(1, 2 * scene.boundingSphere.radius);
|
|
||||||
position.copy(xrCamera.position)
|
|
||||||
.add(cameraDirection.multiplyScalar(radius));
|
|
||||||
|
|
||||||
this.updateTarget();
|
|
||||||
const target = scene.getTarget();
|
|
||||||
position.add(target).sub(this.oldTarget);
|
|
||||||
|
|
||||||
this.goalPosition.copy(position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.placementComplete = true;
|
||||||
|
scene.visible = true;
|
||||||
scene.setHotspotsVisibility(true);
|
scene.setHotspotsVisibility(true);
|
||||||
scene.visible = true; // Model is properly oriented, show it
|
|
||||||
|
if (this.placementBox) {
|
||||||
if (this.xrMode === XRMode.SCREEN_SPACE) {
|
this.placementBox.show = false;
|
||||||
const {session} = this.frame!;
|
}
|
||||||
session.addEventListener('selectstart', this.onSelectStart);
|
|
||||||
session.addEventListener('selectend', this.onSelectEnd);
|
this.presentedScene?.setShadowIntensity(AR_SHADOW_INTENSITY);
|
||||||
session.requestHitTestSourceForTransientInput!({profile: 'generic-touchscreen'})!
|
this.dispatchEvent({ type: 'status', status: ARStatus.OBJECT_PLACED });
|
||||||
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
|
||||||
|
if (this.isInteractionEnabled) {
|
||||||
|
if (this.xrMode === XRMode.SCREEN_SPACE) {
|
||||||
|
const { session } = this.frame!;
|
||||||
|
session.addEventListener('selectstart', this.onSelectStart);
|
||||||
|
session.addEventListener('selectend', this.onSelectEnd);
|
||||||
|
session.requestHitTestSourceForTransientInput!({ profile: 'generic-touchscreen' })!
|
||||||
|
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkForDeferredCeilingPlacement(): void {
|
private checkForDeferredCeilingPlacement(): void {
|
||||||
// Check on every frame—both XR modes, only when ceiling is the target and the model is hidden
|
// Check on every frame—both XR modes, only when ceiling is the target and the model is hidden
|
||||||
if (!this.placeOnCeiling || !this.presentedScene || this.presentedScene.visible) return;
|
if (!this.placeOnCeiling || !this.presentedScene || this.presentedScene.visible) return;
|
||||||
|
|
@ -952,17 +953,23 @@ export class ARRenderer extends EventDispatcher<
|
||||||
scene.pivot.position.add(target).sub(this.oldTarget);
|
scene.pivot.position.add(target).sub(this.oldTarget);
|
||||||
this.goalPosition.copy(scene.pivot.position);
|
this.goalPosition.copy(scene.pivot.position);
|
||||||
// Setup touch interaction if needed
|
// Setup touch interaction if needed
|
||||||
const {session} = this.frame!;
|
if (this.isInteractionEnabled) {
|
||||||
session.addEventListener('selectstart', this.onSelectStart);
|
const { session } = this.frame!;
|
||||||
session.addEventListener('selectend', this.onSelectEnd);
|
session.addEventListener('selectstart', this.onSelectStart);
|
||||||
session.requestHitTestSourceForTransientInput!({profile: 'generic-touchscreen'})!
|
session.addEventListener('selectend', this.onSelectEnd);
|
||||||
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
session.requestHitTestSourceForTransientInput!({ profile: 'generic-touchscreen' })!
|
||||||
|
.then(hitTestSource => { this.transientHitTestSource = hitTestSource; });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this.placementComplete = true;
|
||||||
|
|
||||||
scene.visible = true;
|
scene.visible = true;
|
||||||
scene.setHotspotsVisibility(true);
|
scene.setHotspotsVisibility(true);
|
||||||
|
scene.setShadowIntensity(AR_SHADOW_INTENSITY);
|
||||||
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private getTouchLocation(): Vector3|null {
|
private getTouchLocation(): Vector3|null {
|
||||||
const {axes} = this.inputSource!.gamepad!;
|
const {axes} = this.inputSource!.gamepad!;
|
||||||
let location = this.placementBox!.getExpandedHit(
|
let location = this.placementBox!.getExpandedHit(
|
||||||
|
|
@ -1014,51 +1021,35 @@ export class ARRenderer extends EventDispatcher<
|
||||||
* – Ceiling: accept downward-facing planes; keep full XYZ and hide model
|
* – Ceiling: accept downward-facing planes; keep full XYZ and hide model
|
||||||
* until a ceiling hit arrives (no premature floor placement).
|
* until a ceiling hit arrives (no premature floor placement).
|
||||||
*/
|
*/
|
||||||
public moveToAnchor(frame: XRFrame) {
|
public moveToAnchor(frame: XRFrame) {
|
||||||
if (this.parsedAnchorOffset != null) {
|
if (this.parsedAnchorOffset != null || this.placementComplete) {
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
// Handle deferred initial placement for ceiling mode
|
|
||||||
if (this.placeOnCeiling &&
|
|
||||||
this.xrMode === XRMode.WORLD_SPACE &&
|
|
||||||
!this.worldSpaceInitialPlacementDone &&
|
|
||||||
!this.presentedScene!.visible) {
|
|
||||||
|
|
||||||
// Check if orientation is now sufficient
|
|
||||||
if (!this.isViewPointingUp()) {
|
|
||||||
console.log('[ARR/moveToAnchor] Still waiting for proper ceiling orientation');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Orientation is good - complete the deferred world-space placement
|
|
||||||
const scene = this.presentedScene!;
|
|
||||||
const xrCamera = scene.getCamera();
|
|
||||||
const {position: optimalPosition, scale: optimalScale} =
|
|
||||||
this.calculateWorldSpaceOptimalPlacement(scene, xrCamera);
|
|
||||||
|
|
||||||
this.goalPosition.copy(optimalPosition);
|
|
||||||
this.goalScale = optimalScale;
|
|
||||||
this.initialModelScale = optimalScale;
|
|
||||||
|
|
||||||
scene.pivot.position.copy(optimalPosition);
|
|
||||||
scene.pivot.scale.set(optimalScale, optimalScale, optimalScale);
|
|
||||||
|
|
||||||
this.worldSpaceInitialPlacementDone = true;
|
|
||||||
this.calculateWorldSpaceScaleLimits(scene);
|
|
||||||
this.enableWorldSpaceUserInteraction();
|
|
||||||
|
|
||||||
scene.visible = true;
|
|
||||||
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip for world-space mode after initial placement (unless ceiling was deferred)
|
|
||||||
if (this.xrMode === XRMode.WORLD_SPACE && this.worldSpaceInitialPlacementDone) {
|
|
||||||
this.placementBox!.show = false;
|
|
||||||
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hitSource = this.initialHitSource;
|
||||||
|
if (hitSource == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitResults = frame.getHitTestResults(hitSource);
|
||||||
|
if (hitResults.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hitResult = hitResults[0];
|
||||||
|
const hitPosition = this.getHitPoint(hitResult);
|
||||||
|
|
||||||
|
if (hitPosition != null) {
|
||||||
|
this.goalPosition.copy(hitPosition);
|
||||||
|
|
||||||
|
this.placementComplete = true;
|
||||||
|
if (this.placementBox) {
|
||||||
|
this.placementBox.show = false;
|
||||||
|
}
|
||||||
|
this.presentedScene!.setShadowIntensity(AR_SHADOW_INTENSITY);
|
||||||
|
this.dispatchEvent({type: 'status', status: ARStatus.OBJECT_PLACED});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private isViewPointingUp(thresholdDeg: number = CEILING_ORIENTATION_THRESHOLD): boolean {
|
private isViewPointingUp(thresholdDeg: number = CEILING_ORIENTATION_THRESHOLD): boolean {
|
||||||
const cam = this.presentedScene!.getCamera();
|
const cam = this.presentedScene!.getCamera();
|
||||||
|
|
@ -1085,6 +1076,9 @@ export class ARRenderer extends EventDispatcher<
|
||||||
|
|
||||||
|
|
||||||
private onSelectStart = (event: Event) => {
|
private onSelectStart = (event: Event) => {
|
||||||
|
if (!this.isInteractionEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const hitSource = this.transientHitTestSource;
|
const hitSource = this.transientHitTestSource;
|
||||||
if (hitSource == null) {
|
if (hitSource == null) {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue