Skip to content

Commit aa10ecd

Browse files
authored
Add auto-gen for r3f comonents (#1515)
1 parent 7253536 commit aa10ecd

9 files changed

Lines changed: 217 additions & 14 deletions

File tree

src/r3f/components/CameraControls.jsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,30 @@ const ControlsBaseComponent = forwardRef( function ControlsBaseComponent( props,
108108

109109
} );
110110

111+
/**
112+
* Wraps the three.js EnvironmentControls class. Automatically attaches to the R3F camera, scene,
113+
* and canvas. All EnvironmentControls properties can be set as props.
114+
* @component
115+
* @param {Object} props
116+
* @param {Camera} [props.camera] - Override the default R3F camera.
117+
* @param {Object3D} [props.scene] - Override the default R3F scene.
118+
* @param {HTMLCanvasElement} [props.domElement] - Override the default canvas element.
119+
*/
111120
export const EnvironmentControls = forwardRef( function EnvironmentControls( props, ref ) {
112121

113122
return <ControlsBaseComponent { ...props } ref={ ref } controlsConstructor={ EnvironmentControlsImpl } />;
114123

115124
} );
116125

126+
/**
127+
* Wraps the three.js GlobeControls class. Must be a child of TilesRenderer to receive ellipsoid
128+
* context. All GlobeControls properties can be set as props.
129+
* @component
130+
* @param {Object} props
131+
* @param {Camera} [props.camera] - Override the default R3F camera.
132+
* @param {Object3D} [props.scene] - Override the default R3F scene.
133+
* @param {HTMLCanvasElement} [props.domElement] - Override the default canvas element.
134+
*/
117135
export const GlobeControls = forwardRef( function GlobeControls( props, ref ) {
118136

119137
return <ControlsBaseComponent { ...props } ref={ ref } controlsConstructor={ GlobeControlsImpl } />;

src/r3f/components/CameraTransition.jsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@ import { CameraTransitionManager } from '3d-tiles-renderer/three';
44
import { useDeepOptions } from '../utilities/useOptions.js';
55
import { useApplyRefs } from '../utilities/useApplyRefs.js';
66

7+
/**
8+
* Manages transitions between perspective and orthographic cameras. Wraps CameraTransitionManager
9+
* and integrates with R3F's camera state. All CameraTransitionManager properties can be set as props.
10+
* @component
11+
* @param {Object} props
12+
* @param {string} [props.mode='perspective'] - Active camera mode: `'perspective'` or `'orthographic'`.
13+
* @param {PerspectiveCamera} [props.perspectiveCamera] - Override the internal perspective camera.
14+
* @param {OrthographicCamera} [props.orthographicCamera] - Override the internal orthographic camera.
15+
* @param {Function} [props.onBeforeToggle] - Called before the camera mode switches, with the manager
16+
* and target camera as arguments. Defaults to syncing via active controls if present.
17+
*/
718
export const CameraTransition = forwardRef( function CameraTransition( props, ref ) {
819

920
const {

src/r3f/components/CanvasDOMOverlay.jsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@ import { useMemo, useEffect, StrictMode, forwardRef, useState } from 'react';
22
import { createRoot } from 'react-dom/client';
33
import { useThree } from '@react-three/fiber';
44

5+
/**
6+
* Creates a DOM overlay positioned absolutely over the canvas. Children are rendered into a
7+
* separate React root. Remaining props are passed to the root div element.
8+
* @component
9+
* @param {Object} props
10+
* @param {ReactNode} [props.children] - DOM content to render in the overlay.
11+
*/
512
// Utility class for overlaying dom elements on top of the canvas
613
export const CanvasDOMOverlay = forwardRef( function CanvasDOMOverlay( { children, ...rest }, ref ) {
714

src/r3f/components/CompassGizmo.jsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ function CompassGraphic( { northColor = 0xEF5350, southColor = 0xFFFFFF } ) {
149149

150150
}
151151

152+
/**
153+
* Renders a compass overlay that rotates to indicate north based on the camera orientation relative
154+
* to the tileset ellipsoid. Must be a child of TilesRenderer. Remaining props are passed to the
155+
* root group element.
156+
* @component
157+
* @param {Object} props
158+
* @param {string} [props.mode='3d'] - Rotation mode: `'3d'` tracks full camera orientation, `'2d'` tracks yaw only.
159+
* @param {number} [props.scale=35] - Size of the compass in pixels.
160+
* @param {number|Array} [props.margin=10] - Margin from the bottom-right corner in pixels. Pass `[x, y]` to set each axis independently.
161+
* @param {boolean} [props.visible=true] - Whether the compass is rendered.
162+
* @param {boolean} [props.overrideRenderLoop] - If true, renders the main scene before drawing the compass overlay.
163+
* @param {ReactNode} [props.children] - Custom compass graphic replacing the default. Should fit within a -0.5 to 0.5 unit cube with +Y pointing north and +X pointing east.
164+
*/
152165
export function CompassGizmo( { children, overrideRenderLoop, mode = '3d', margin = 10, scale = 35, visible = true, ...rest } ) {
153166

154167
const [ defaultCamera, defaultScene, size ] = useThree( state => [ state.camera, state.scene, state.size ] );

src/r3f/components/SettledObjects.jsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ const QueryManagerContext = createContext( null );
1212
const _matrix = /* @__PURE__ */ new Matrix4();
1313
const _ray = /* @__PURE__ */ new Ray();
1414

15+
/**
16+
* A SettledObject that smoothly interpolates its position as the query result updates.
17+
* Must be a descendant of SettledObjects.
18+
* @component
19+
* @param {Object} props
20+
* @param {number} [props.interpolationFactor=0.025] - Controls interpolation speed. Smaller values produce slower, smoother movement.
21+
* @param {Function} [props.onQueryUpdate] - Called with the raycast hit result each time the query updates.
22+
*/
1523
export const AnimatedSettledObject = forwardRef( function AnimatedSettledObject( props, ref ) {
1624

1725
const {
@@ -115,6 +123,18 @@ export const AnimatedSettledObject = forwardRef( function AnimatedSettledObject(
115123

116124
} );
117125

126+
/**
127+
* Positions a component on the surface of the tileset at a lat/lon coordinate or along a ray.
128+
* Must be a descendant of SettledObjects.
129+
* @component
130+
* @param {Object} props
131+
* @param {ReactNode} [props.component=<group/>] - The element to clone and position on the surface.
132+
* @param {number} [props.lat=null] - Latitude in radians. Use with `lon` for geographic positioning.
133+
* @param {number} [props.lon=null] - Longitude in radians. Use with `lat` for geographic positioning.
134+
* @param {Vector3} [props.rayorigin=null] - Ray origin for arbitrary ray-based positioning.
135+
* @param {Vector3} [props.raydirection=null] - Ray direction for arbitrary ray-based positioning.
136+
* @param {Function} [props.onQueryUpdate] - Called with the raycast hit result each time the query updates.
137+
*/
118138
// Object that updates its "settled" state
119139
export const SettledObject = forwardRef( function SettledObject( props, ref ) {
120140

@@ -184,6 +204,14 @@ export const SettledObject = forwardRef( function SettledObject( props, ref ) {
184204

185205
} );
186206

207+
/**
208+
* Manages raycasting queries against the tileset for positioning child SettledObject components.
209+
* Must be a child of TilesRenderer. All QueryManager properties can be set as props.
210+
* @component
211+
* @param {Object} props
212+
* @param {Object3D|Array} [props.scene] - Scene(s) to raycast against. Defaults to the R3F scene.
213+
* @param {ReactNode} [props.children] - SettledObject or AnimatedSettledObject components.
214+
*/
187215
export const SettledObjects = forwardRef( function SettledObjects( props, ref ) {
188216

189217
const threeScene = useThree( ( { scene } ) => scene );

src/r3f/components/TilesAttributionOverlay.jsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ function randomID() {
88

99
}
1010

11+
/**
12+
* Displays attributions collected from the loaded tileset. Must be a child of TilesRenderer.
13+
* Remaining props are passed to the underlying CanvasDOMOverlay element.
14+
* @component
15+
* @param {Object} props
16+
* @param {Function} [props.generateAttributions] - Custom function to generate attribution elements.
17+
* Receives the attributions array and the overlay element's unique id. Defaults to built-in rendering.
18+
* @param {Object} [props.style] - Style overrides applied to the overlay container.
19+
* @param {ReactNode} [props.children] - Additional content rendered above the attributions.
20+
*/
1121
// Overlay for displaying tile data set attributions
1222
export function TilesAttributionOverlay( { children, style, generateAttributions, ...rest } ) {
1323

src/r3f/components/TilesRenderer.jsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,20 @@ function TileSetRoot( { children } ) {
3232

3333
}
3434

35+
/**
36+
* Creates a group positioned and oriented at a geographic coordinate on the tileset ellipsoid.
37+
* Must be a child of TilesRenderer. Does not modify the tileset transform.
38+
* @component
39+
* @param {Object} props
40+
* @param {number} [props.lat=0] - Latitude in radians.
41+
* @param {number} [props.lon=0] - Longitude in radians.
42+
* @param {number} [props.height=0] - Height above the ellipsoid in meters.
43+
* @param {number} [props.az=0] - Azimuth rotation in radians, applied first.
44+
* @param {number} [props.el=0] - Elevation rotation in radians, applied second.
45+
* @param {number} [props.roll=0] - Roll rotation in radians, applied third.
46+
* @param {Ellipsoid} [props.ellipsoid] - Ellipsoid to use when no TilesRenderer parent is present.
47+
* @param {ReactNode} [props.children] - Children positioned relative to the east-north-up frame.
48+
*/
3549
export function EastNorthUpFrame( props ) {
3650

3751
const {
@@ -142,6 +156,16 @@ export function EastNorthUpFrame( props ) {
142156

143157
}
144158

159+
/**
160+
* Registers a plugin on the nearest parent TilesRenderer. Must be a child of TilesRenderer.
161+
* All properties on the plugin instance can be set as props directly. Note that some plugin
162+
* properties cannot be changed after construction.
163+
* @component
164+
* @param {Object} props
165+
* @param {Function} props.plugin - The plugin class to instantiate.
166+
* @param {Object|Array} [props.args] - Constructor arguments: an object (single arg) or array (spread as multiple args).
167+
* @param {ReactNode} [props.children] - Children rendered once the plugin is registered.
168+
*/
145169
// component for registering a plugin
146170
export const TilesPlugin = forwardRef( function TilesPlugin( props, ref ) {
147171

@@ -221,6 +245,17 @@ export const TilesPlugin = forwardRef( function TilesPlugin( props, ref ) {
221245

222246
} );
223247

248+
/**
249+
* Wrapper for the three.js TilesRenderer class. All properties on the TilesRenderer instance can
250+
* be set as props using dot-notation for nested properties (e.g. `lruCache-minSize`). Events are
251+
* registered with a camel-cased `on` prefix (e.g. `onLoadModel`).
252+
* @component
253+
* @param {Object} props
254+
* @param {string} [props.url] - URL of the tileset to load.
255+
* @param {boolean} [props.enabled=true] - If false, `update` is not called on the renderer each frame.
256+
* @param {Object} [props.group] - Props applied to the root Three.js group of the tileset.
257+
* @param {ReactNode} [props.children] - Child components such as TilesPlugin, GlobeControls, etc.
258+
*/
224259
// component for adding a TilesRenderer to the scene
225260
export const TilesRenderer = forwardRef( function TilesRenderer( props, ref ) {
226261

utils/docs/RenderDocsUtils.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,71 @@ export function renderEvents( events, callbackMap = {} ) {
332332

333333
}
334334

335+
export function renderComponent( doc, callbackMap = {} ) {
336+
337+
const lines = [];
338+
339+
lines.push( `## ${ doc.name }` );
340+
lines.push( '' );
341+
342+
if ( doc.description ) {
343+
344+
lines.push( doc.description );
345+
lines.push( '' );
346+
347+
}
348+
349+
const props = ( doc.params || [] ).filter( p => p.name.includes( '.' ) );
350+
351+
if ( props.length > 0 ) {
352+
353+
lines.push( '### Props' );
354+
lines.push( '' );
355+
lines.push( '```jsx' );
356+
lines.push( `<${ doc.name }` );
357+
358+
for ( const prop of props ) {
359+
360+
const name = prop.name.split( '.' ).pop();
361+
const type = formatType( prop.type, callbackMap );
362+
const optional = prop.optional ? '?' : '';
363+
const defStr = prop.defaultvalue !== undefined ? ` = ${ prop.defaultvalue }` : '';
364+
lines.push( `\t${ name }${ optional }: ${ type }${ defStr }` );
365+
366+
}
367+
368+
lines.push( '/>' );
369+
lines.push( '```' );
370+
lines.push( '' );
371+
372+
for ( const prop of props ) {
373+
374+
const name = prop.name.split( '.' ).pop();
375+
const type = formatType( prop.type, callbackMap );
376+
const optional = prop.optional ? '?' : '';
377+
const defStr = prop.defaultvalue !== undefined ? ` = ${ prop.defaultvalue }` : '';
378+
lines.push( `### .${ name }` );
379+
lines.push( '' );
380+
lines.push( '```jsx' );
381+
lines.push( `${ name }${ optional }: ${ type }${ defStr }` );
382+
lines.push( '```' );
383+
lines.push( '' );
384+
385+
if ( prop.description ) {
386+
387+
lines.push( prop.description );
388+
lines.push( '' );
389+
390+
}
391+
392+
}
393+
394+
}
395+
396+
return lines.join( '\n' );
397+
398+
}
399+
335400
export function renderClass( classDoc, members, callbackMap = {}, resolveLink = null ) {
336401

337402
const lines = [];

utils/docs/build.js

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { execSync } from 'child_process';
22
import fs from 'fs';
33
import path from 'path';
4-
import { renderClass, renderTypedef, renderConstants, toAnchor } from './RenderDocsUtils.js';
4+
import { renderClass, renderComponent, renderTypedef, renderConstants, toAnchor } from './RenderDocsUtils.js';
55
import { findRootDir } from '../CommandUtils.js';
66

77
const ROOT_DIR = findRootDir();
@@ -32,11 +32,11 @@ const ENTRY_POINTS = [
3232
title: '3d-tiles-renderer/core/plugins',
3333
source: 'src/core/plugins',
3434
},
35-
// {
36-
// output: 'src/r3f/API.md',
37-
// title: '3d-tiles-renderer/r3f',
38-
// source: null,
39-
// },
35+
{
36+
output: 'src/r3f/API.md',
37+
title: '3d-tiles-renderer/r3f',
38+
source: 'src/r3f/components',
39+
},
4040
];
4141

4242
// Run JSDoc for all entry points and build a global type registry for cross-file links
@@ -45,15 +45,20 @@ const results = ENTRY_POINTS.map( entry => ( {
4545
jsdoc: filterDocumented( runJsDoc( path.resolve( ROOT_DIR, entry.source ) ) )
4646
} ) );
4747

48-
// Only classes and non-callback typedefs get sections (and therefore anchors) in the output.
48+
// Doclet type predicates
49+
const isClass = d => d.kind === 'class';
50+
const isObjectTypedef = d => d.kind === 'typedef' && d.type.names[ 0 ] !== 'function';
51+
const isCallbackTypedef = d => d.kind === 'typedef' && d.type.names[ 0 ] === 'function';
52+
const isReactComponent = d => ( d.kind === 'function' || d.kind === 'constant' ) && d.tags && d.tags.some( t => t.title === 'component' );
53+
const isConstant = d => d.kind === 'constant' && ! d.memberof && ! isReactComponent( d );
54+
55+
// Only classes, non-callback typedefs, and React components get sections (and therefore anchors) in the output.
4956
const typeRegistry = {}; // name -> output path
5057
for ( const { entry, jsdoc } of results ) {
5158

5259
for ( const d of jsdoc ) {
5360

54-
const isClass = d.kind === 'class';
55-
const isObjectTypedef = ( d.kind === 'typedef' && d.type.names[ 0 ] !== 'function' );
56-
if ( isClass || isObjectTypedef ) {
61+
if ( isClass( d ) || isObjectTypedef( d ) || isReactComponent( d ) ) {
5762

5863
typeRegistry[ d.name ] = entry.output;
5964

@@ -94,7 +99,7 @@ for ( const { entry, jsdoc } of results ) {
9499

95100
// Sort classes so base classes appear before subclasses
96101
const classes = jsdoc
97-
.filter( d => d.kind === 'class' )
102+
.filter( d => isClass( d ) )
98103
.sort( ( a, b ) => {
99104

100105
const aIsBase = ! a.augments || a.augments.length === 0;
@@ -109,7 +114,7 @@ for ( const { entry, jsdoc } of results ) {
109114
const callbackMap = {};
110115
for ( const d of jsdoc ) {
111116

112-
if ( d.kind === 'typedef' && d.type.names[ 0 ] === 'function' ) {
117+
if ( isCallbackTypedef( d ) ) {
113118

114119
callbackMap[ d.name ] = d;
115120

@@ -119,7 +124,7 @@ for ( const { entry, jsdoc } of results ) {
119124

120125
// Sort typedefs so plain-object bases appear before derived types; exclude @callback entries
121126
const typedefs = jsdoc
122-
.filter( d => d.kind === 'typedef' && d.type.names[ 0 ] !== 'function' )
127+
.filter( d => isObjectTypedef( d ) )
123128
.sort( ( a, b ) => {
124129

125130
const aIsBase = a.type.names[ 0 ] === 'Object';
@@ -130,9 +135,14 @@ for ( const { entry, jsdoc } of results ) {
130135

131136
} );
132137

138+
// sort components by source line order
139+
const components = jsdoc
140+
.filter( d => isReactComponent( d ) )
141+
.sort( ( a, b ) => a.meta.lineno - b.meta.lineno );
142+
133143
// sort constants by source line order
134144
const constants = jsdoc
135-
.filter( d => d.kind === 'constant' && ! d.memberof )
145+
.filter( d => isConstant( d ) )
136146
.sort( ( a, b ) => a.meta.lineno - b.meta.lineno );
137147

138148
// cache all fields by associated class name
@@ -158,6 +168,12 @@ for ( const { entry, jsdoc } of results ) {
158168

159169
sections.push( renderConstants( constants, callbackMap ) );
160170

171+
for ( const component of components ) {
172+
173+
sections.push( renderComponent( component, callbackMap ) );
174+
175+
}
176+
161177
for ( const cls of classes ) {
162178

163179
sections.push( renderClass( cls, classMembers[ cls.name ] || [], callbackMap, resolveLink ) );

0 commit comments

Comments
 (0)