Points in 3D space
Previous chapter recalls: Status Recording
- You know how to record and restore operations through State.
- Knowingly use State related methods and events.
- Learn about the Live SDK event system.
- Gets a three-dimensional position to a point.
Preparatory work
We create a new directory (src/4.points-in-3d
and corresponding htm file and jsx or tsx file. The State code with the previous chapter is too onerous, and we show the base development of the contents of the three-dimensional space chapter from .
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>points in 3 dimensions | Points in 3d</title>
<style>
* { margin: 0; padding: 0; }
html, body #app { width: 100%; height: 100%; overflow: hidden; }
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index"></script>
</body>
</html>
- JavaScript
- TypeScript
import React, { Component } from "react";
import { parseWork } from "@realsee/five";
/**
* React HOC Get the address of work
* @param url work.json
*/
function withFetchWork(url) {
return function(Compnent) {
return class extends Component {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
export { withFetchWork };
import React, { Component } from "react";
/**
* React HOC: Get the size of the current window
*/
function withWindowDimensions() {
return function(Compnent) {
return class extends Component {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
export { withWindowDimensions };
import React, { Component } from "react";
import { Five } from "@realsee/five";
import { withFive, createFiveFeature } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";
const FEATURES = createFiveFeature("currentState", "setState");
/**
* React Component: Modal Control
*/
const ModeController = compose(
withFive(FEATURES)
)(class extends Component {
render() {
return <Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={this.props.$five.currentState.mode}
onChange={(_, newValue) => {
this.props.$five.setState({ mode: newValue });
}}
>
<BottomNavigationAction label="Panorama roaming" icon={<DirectionsWalkIcon/>} value={Five.Mode.Panorama}/>
<BottomNavigationAction label="Space overview" icon={<ViewInArIcon/>} value={Five.Mode.Floorplan}/>
</BottomNavigation>
</Paper>;
}
})
export { ModeController };
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
import { ModeController } from "./ModeController";
/** Data URL of work.json */
const workURL = "https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
<ModeController/>;
</FiveProvider>;
}
});
export { App };
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};
import React, { Component, ComponentClass } from "react";
import { Work, parseWork } from "@realsee/five";
/**
* React HOC for work
* @param url work. The address of the son
*/
function withFetchWork<P extends Record<string, any>>(url: string) {
return function(Compnent: ComponentClass<P & { work: Work }>): ComponentClass<P> {
return class extends Component<P, {work: Work | null}> {
state = { work: null };
componentDidMount() {
fetch(url).then(res => res.json()).then(json => {
this.setState({ work: parseWork(json) });
});
}
render() {
if (this.state.work === null) return null;
return <Compnent work={this.state.work} {...this.props}/>;
}
}
}
}
export { withFetchWork };
import React, { Component, ComponentClass } from "react";
/**
* React HOC: Get the current window size
*/
function withWindowDimensions<P extends Record<string, any>>() {
return function(Compnent: ComponentClass<P & { windowDimensions: { width: number, height: number} }>): ComponentClass<P> {
return class extends Component<P, {width: number, height: number}> {
state = this.getWindowDimensions();
resizeListener = () => {
this.setState(this.getWindowDimensions());
};
getWindowDimensions() {
return { width: window.innerWidth, height: window.innerHeight };
}
componentDidMount() {
window.addEventListener("resize", this.resizeListener, false);
}
componentWillUnmount() {
window.removeEventListener("resize", this.resizeListener, false);
}
render() {
const dimensions = { width: this.state.width, height: this.state.height };
return <Compnent windowDimensions={dimensions} {...this.props}/>;
}
}
}
}
export { withWindowDimensions };
import React, { Component } from "react";
import { Five, Mode } from "@realsee/five";
import { withFive, createFiveFeature, PropTypeOfFiveFeatures } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import BottomNavigation from "@mui/material/BottomNavigation";
import BottomNavigationAction from "@mui/material/BottomNavigationAction";
import Paper from "@mui/material/Paper";
import DirectionsWalkIcon from "@mui/icons-material/DirectionsWalk";
import ViewInArIcon from "@mui/icons-material/ViewInAr";
const FEATURES = createFiveFeature("currentState", "setState");
type Props = PropTypeOfFiveFeatures<typeof FEATURES>;
/**
* React Component: Model Control
*/
const ModeController = compose(
withFive(FEATURES)
)(class extends Component<Props> {
render() {
return <Paper sx={{ position: "fixed", bottom: 0, left: 0, right: 0 }}>
<BottomNavigation
showLabels
value={this.props.$five.currentState.mode}
onChange={(_, newValue: Mode) => {
this.props.$five.setState({ mode: newValue });
}}
>
<BottomNavigationAction label="Panorama roaming" icon={<DirectionsWalkIcon/>} value={Five.Mode.Panorama}/>
<BottomNavigationAction label="Space overview" icon={<ViewInArIcon/>} value={Five.Mode.Floorplan}/>
</BottomNavigation>
</Paper>;
}
})
export { ModeController };
import React, { Component } from "react";
import { Work } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
import { ModeController } from "./ModeController";
/** data URL of work.json */
const workURL = "https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component<{work: Work, windowDimensions: { width: number, height: number }}> {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
<ModeController/>
</FiveProvider>;
}
});
export { App };
import React from "react";
import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App/>, document.querySelector("#app"));
export {};
Start service npm run dev
and jump to the current page "http://localhost:3000/src/4.points-in-3d/index.html".
Please see your console. The port number will change due to your configuration and current port occupancy, please check the console output. If you use another development build tool, please start the service as required by your own development build tool.
Event System
When clicking on the screen, the default behavior offive SDK is to select the most appropriate observation near the location of the clicked to move the past.This is true of most users' actions, and the processing logic of A tag is mostly linked to the link jumps.The above is the builtin of five SDK tapGestureevents.
Built-in Events
five SDK built-in events are as follows:
- tapGesture: left mouse click or finger.Default behavior is a point move.
- panGest: mouse over one mouse or drag and drop the finger on the screen.Camera rotation (camera shift under Topview).
- pinchGesture: finger to make fabricated gestures.The default behavior is to modify camera visualizations.
- mouseWheel: Mouse Wheel.The default behavior is to modify camera visualizations.
- gesture: any of the events above.
Block default behavior
All events and browser's handling logic for A tag can block default events, you simply listen to wants
at the beginning of the callback function return false
.For example, want to block the default point movement of tapGesture.This can be done as follows.
this.props.$five.on("wantsTapGesure", () => {
/// highlight-start
// Block tapGest trigger
return false;
});
The API for each event can view detailed documents
Get coordinates from tapGesture
We make a simple feature to mark the 3-D position on the canvas. But in order not to conflict with the point move feature, we use a
Switch
button to control whether or not the marker status is enabled.
Header Add Dependencies
This chapter needs to introduce three.js.three.js is a three-dimensional graphics library,Five SDK uses three.js.This chapter is related to three.js and make some instructions here, you don't need to be fully aware of three.js, I can understand you by making some instructions.
THREE.Vector3
: you can think of a structure that is{ x: number, y: number, z: number }
and add some mathematical methods (this time won't use a mathematical method, just record xyz)THREE.Raycaster
: light projecting class.You can simply understand that a point on the screen corresponding to a three-dimensional space is a ray.
;
The rays have many effects, such as:passing the intersection test before the ray and model to determine if the object is selected.
Write MarkController Component
- Add a MarkController file to write components.
- We control whether the current app is in marked mode by active React state status.
- By
tapGesture
the first parameter israycaster
, bymodelIntersectRaycaster
the focus information can be retrievedintersect
,intersect.point
is the coordinates of the node. - Save all interfaces with
marks
React state and implement collection and deletion.
- JavaScript
- TypeScript
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { withFive, createFiveFeature } from "@realsee/five/react";
import Button from "@mui/material/Button";
import Switch from "@mui/material/Switch";
import Chip from "@mui/material/Chip";
import Stack from "@mui/material/Stack";
import Paper from "@mui/material/Paper";
const FEATURES = createFiveFeature("intersectRaycaster", "on", "off");
const MarkController = compose(
withFive(FEATURES)
)(class extends Component {
state = { active: false, marks: [] };
onTapGesture = (raycaster) => {
if (this.state.active) {
const [intersect] = this.props.$five.intersectRaycaster(raycaster);
if (intersect) this.setState({ marks: this.state.marks.concat(intersect.point) });
return false;
}
};
componentDidMount() {
this.props.$five.on("wantsTapGesture", this.onTapGesture);
}
componentWillUnmount() {
this.props.$five.off("wantsTapGesture", this.onTapGesture);
}
render() {
return <Paper sx={{ position: "fixed", top: 10, left: 10, padding: 1 }}>
<Stack>
<Stack direction="row">
<Switch
checked={this.state.active}
onChange={(event, checked) => this.setState({ active: checked })}
/> <Button disabled>开启点击记录坐标</Button>
</Stack>
<Stack spacing={1}>
{this.state.marks.map((point, index) => {
const { x, y, z } = point;
return <Chip
key={index}
label={`x=${x.toFixed(2)} y=${y.toFixed(2)} z=${z.toFixed(2)}`}
onDelete={() => this.setState({marks: this.state.marks.filter((_, index_) => index_ !== index)})}
/>
})}
</Stack>
</Stack>
</Paper>
}
});
export { MarkController };
import * as THREE from "three";
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { EventCallback } from "@realsee/five";
import { withFive, createFiveFeature, PropTypeOfFiveFeatures } from "@realsee/five/react";
import Button from "@mui/material/Button";
import Switch from "@mui/material/Switch";
import Chip from "@mui/material/Chip";
import Stack from "@mui/material/Stack";
import Paper from "@mui/material/Paper";
const FEATURES = createFiveFeature("intersectRaycaster", "on", "off");
type Props = PropTypeOfFiveFeatures<typeof FEATURES>;
type State = {
active: boolean,
marks: THREE.Vector3[],
};
const MarkController = compose(
withFive(FEATURES)
)(class extends Component<Props, State> {
state: State = { active: false, marks: [] };
onTapGesture: EventCallback["wantsTapGesture"] = (raycaster) => {
if (this.state.active) {
const [intersect] = this.props.$five.intersectRaycaster(raycaster);
if (intersect) this.setState({ marks: this.state.marks.concat(intersect.point) });
return false;
}
};
componentDidMount() {
this.props.$five.on("wantsTapGesture", this.onTapGesture);
}
componentWillUnmount() {
this.props.$five.off("wantsTapGesture", this.onTapGesture);
}
render() {
return <Paper sx={{ position: "fixed", top: 10, left: 10, padding: 1 }}>
<Stack>
<Stack direction="row">
<Switch
checked={this.state.active}
onChange={(event, checked) => this.setState({ active: checked })}
/> <Button disabled>open record coordinate</Button>
</Stack>
<Stack spacing={1}>
{this.state.marks.map((point, index) => {
const { x, y, z } = point;
return <Chip
key={index}
label={`x=${x.toFixed(2)} y=${y.toFixed(2)} z=${z.toFixed(2)}`}
onDelete={() => this.setState({marks: this.state.marks.filter((_, index_) => index_ !== index)})}
/>
})}
</Stack>
</Stack>
</Paper>
}
});
export { MarkController };
Use Tag Component
Insert into App file GiveProvider
- JavaScript
- TypeScript
import React, { Component } from "react";
import { compose } from "@wordpress/compose";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
import { ModeController } from "./ModeController";
import { MarkController } from "./MarkController";
/** data URL of work.json */
const workURL = "https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
<ModeController/>;
<MarkController/>;
</FiveProvider>;
}
});
export { App };
import React, { Component } from "react";
import { Work } from "@realsee/five";
import { createFiveProvider, FiveCanvas } from "@realsee/five/react";
import { compose } from "@wordpress/compose";
import { withFetchWork } from "./withFetchWork";
import { withWindowDimensions } from "./withWindowDimensions";
import { ModeController } from "./ModeController";
import { MarkController } from "./MarkController";
/** data URL of work.json */
const workURL = "https://vrlab-public.ljcdn.com/release/static/image/release/five/work-sample/07bdc58f413bc5494f05c7cbb5cbdce4/work.json";
const FiveProvider = createFiveProvider();
const App = compose(
withFetchWork(workURL),
withWindowDimensions()
)(class extends Component<{work: Work, windowDimensions: { width: number, height: number }}> {
render() {
const { work, windowDimensions } = this.props;
return <FiveProvider initialWork={work}>
<FiveCanvas width={windowDimensions.width} height={windowDimensions.height}/>
<ModeController/>
<MarkController/>
</FiveProvider>;
}
});
export { App };
Go back to your browser and find a switch in the upper left corner of your page.Turn on the switch, tap on the content of the canvas and output the coordinates of the click position.
Graby, understand and get three-dimensional coordinates: partying_face: