Tag
Previous chapter recalls: points in three dimensions
you understand the Live SDK event system and try to develop a small app to get a point of three-dimensional location by clicking on the event.
Set labels in three-dimensional space.
Preparatory work
We create a new directory (src/5.tagging
and corresponding htm files and jsx or tsx files.
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>Tag | Tagging</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 fetch work
* @param url address of 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/5.tagging/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.
Developing Tag Features
Add Tag Style
Add a tag style to the html.
Style is not necessary, it is only for better labels.
<!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>Tag | Tagging</title>
<style>
* { margin: 0; padding: 0; }
html, body #app { width: 100%; height: 100%; overflow: hidden; }
.tag { position: absolute; width: 0; height: 0; transform: translateZ(0); }
.tag-pannel { position: absolute; width: 100px; min-height: 20px; transform: translate(-50%, 0); left: 50%; bottom: 10px; background: #333; color: #fff; border-radius: 2px; text-align: center; line-height: 20px; padding: 8px; font-size: 14px;}
.tag-pannel:after { content: ""; display: block; position: absolute; width: 10px; height: 10px; left: 50%; bottom: -5px; transform: translate(-50%, 0) rotate(45deg); background: #333; pointer-events: none; }
</style>
</head>
<body>
<div id="app"></div>
<script type="module" src="./index"></script>
</body>
</html>
useFiveProject2d Description
This chapter will use the useFiveProject2d
method.He can match the three-dimensional coordinates to the screen.
$five.project2d(vector: THREE.Vector3, testModel: boolean): THREE.Vector2 | null
- Incoming a three-dimensional coordinates to get a 2D coordinates of the screen, starting at the top of the left in pixels.Can be used as
{ left: returnValue.x + "px", top: returnValue.y + "px" }
etc. - If three-dimensional coordinates cannot be calculated in the screen (e.g. behind or blocked), then return
null
. - The second parameter testModel is calculated if the model collided, i.e. if the coordinate blocked by the model is
null
.
Write TaggingController
- Add a TaggingController file to write components.
- Store labels and text with
tags
React state - Store newly created tags with
newTag
React state - Listen to the
intersectionOnModelUpdate
event to place the newly created tab in the mouse position. - Call
$five.project2
method (tagElement
method) to get screen canvas coordinates and render by changing styles.
- 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 Paper from "@mui/material/Paper";
const FEATURES = createFiveFeature("project2d", "currentState", "on", "off");
/**
* React Component: Tag
*/
const TaggingController = compose(
withFive(FEATURES)
)(class extends Component {
state = { tags: [], newTag: null };
addTag = ( ) => {
this.setState({ newTag: { label: window.prompt("Add Tag", "") || "Untitled" } });
}
tagElement(tag, key) {
const position = tag.position && this.props.$five.project2d(tag.position, true);
const style = position ? { left: position.x, top: position.y } : { display: "none" };
return <div className="tag" style={style} key={key}>
<div className="tag-pannel"><span className="tag-content">{tag.label}</span></div>
</div>
}
onIntersectionUpdate = intersect => {
if (this.state.newTag) this.setState({ newTag: { position: intersect.point, label: this.state.newTag.label } });
};
onTapGesture = () => {
if (this.state.newTag && this.state.newTag.position) {
this.setState({
tags: this.state.tags.concat(this.state.newTag),
newTag: null
});
return false;
}
};
componentDidMount() {
this.props.$five.on("intersectionOnModelUpdate", this.onIntersectionUpdate);
this.props.$five.on("wantsTapGesture", this.onTapGesture);
}
componentWillUnmount() {
this.props.$five.off("intersectionOnModelUpdate", this.onIntersectionUpdate);
this.props.$five.off("wantsTapGesture", this.onTapGesture);
}
render() {
return <React.Fragment>
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<Button onClick={this.addTag}>Tag</Button>
</Paper>
{this.state.newTag && this.tagElement(this.state.newTag)}
{this.state.tags.map((tag, index) => this.tagElement(tag, index))}
</React.Fragment>;
}
});
export { TaggingController };
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 Paper from "@mui/material/Paper";
type Tag = { position?: THREE.Vector3, label: string };
const FEATURES = createFiveFeature("project2d", "currentState", "on", "off");
type Props = PropTypeOfFiveFeatures<typeof FEATURES>;
type State = {
tags: Tag[],
newTag: Tag | null
};
/**
* React Compont: Tag
*/
const TaggingController = compose(
withFive(FEATURES)
)(class extends Component<Props, State> {
state: State = { tags: [], newTag: null };
addTag = () => {
this.setState({ newTag: { label: window.prompt("添加标签", "") || "未命名" } });
}
tagElement(tag: Tag, key?: number | string) {
const position = tag.position && this.props.$five.project2d(tag.position, true);
const style = position ? { left: position.x, top: position.y } : { display: "none" };
return <div className="tag" style={style} key={key}>
<div className="tag-pannel"><span className="tag-content">{tag.label}</span></div>
</div>
}
onIntersectionUpdate: EventCallback["intersectionOnModelUpdate"] = intersect => {
if (this.state.newTag) this.setState({ newTag: { position: intersect.point, label: this.state.newTag.label } });
};
onTapGesture: EventCallback["wantsTapGesture"] = () => {
if (this.state.newTag && this.state.newTag.position) {
this.setState({
tags: this.state.tags.concat(this.state.newTag),
newTag: null
});
return false;
}
};
componentDidMount() {
this.props.$five.on("intersectionOnModelUpdate", this.onIntersectionUpdate);
this.props.$five.on("wantsTapGesture", this.onTapGesture);
}
componentWillUnmount() {
this.props.$five.off("intersectionOnModelUpdate", this.onIntersectionUpdate);
this.props.$five.off("wantsTapGesture", this.onTapGesture);
}
render() {
return <React.Fragment>
<Paper sx={{ position: "fixed", top: 10, left: 10 }}>
<Button onClick={this.addTag}>Tag</Button>
</Paper>
{this.state.newTag && this.tagElement(this.state.newTag)}
{this.state.tags.map((tag, index) => this.tagElement(tag, index))}
</React.Fragment>;
}
});
export { TaggingController };
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 { TaggingController } from "./TaggingController";
/** 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/>;
<TaggingController/>;
</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 { TaggingController } from "./TaggingController";
/** 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/>
<TaggingController/>
</FiveProvider>;
}
});
export { App };
Back to your browser to view it, you will find a tabs button in the upper left corner of your page, click on, fill in the tag name, then move the mouse and click in the position you need.Tag can be placed.
Well, it is a functional feature: partying_face: