diff --git a/editor/index.html b/editor/index.html
index e463ceea5c79e78a79f8f412750df6c51863983a..302fdc86302117a5fe0f0b7c5a0aa85a1ad12657 100644
--- a/editor/index.html
+++ b/editor/index.html
@@ -119,6 +119,7 @@
+
diff --git a/editor/js/Menubar.Add.js b/editor/js/Menubar.Add.js
index 35eefd48ac923b49cf1a06a9b1f98cdc4c65ded2..eee2105db73239792f1076b7716b623203de1523 100644
--- a/editor/js/Menubar.Add.js
+++ b/editor/js/Menubar.Add.js
@@ -242,6 +242,39 @@ Menubar.Add = function ( editor ) {
options.add( option );
*/
+ // Lathe
+
+ var option = new UI.Row();
+ option.setClass( 'option' );
+ option.setTextContent( 'Lathe geometry' );
+ option.onClick( function() {
+
+ var points = [];
+ var segments = 20;
+ var phiStart = 0;
+ var phiLength = 2 * Math.PI;
+
+ points.push( new THREE.Vector3( 0, 0, 0 ) );
+ points.push( new THREE.Vector3( 4, 0, 0 ) );
+ points.push( new THREE.Vector3( 3.5, 0, 0.5 ) );
+ points.push( new THREE.Vector3( 1, 0, 0.75 ) );
+ points.push( new THREE.Vector3( 0.8, 0, 1 ) );
+ points.push( new THREE.Vector3( 0.8, 0, 4 ) );
+ points.push( new THREE.Vector3( 1, 0, 4.2 ) );
+ points.push( new THREE.Vector3( 1.4, 0, 4.8 ) );
+ points.push( new THREE.Vector3( 2, 0, 5 ) );
+ points.push( new THREE.Vector3( 2.5, 0, 5.4 ) );
+ points.push( new THREE.Vector3( 2.5, 0, 12 ) );
+
+ var geometry = new THREE.LatheGeometry( points, segments, phiStart, phiLength );
+ var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial( { side: THREE.DoubleSide } ) );
+ mesh.name = 'Lathe ' + ( ++meshCount );
+
+ editor.execute( new AddObjectCommand( mesh ) );
+
+ } );
+ options.add( option );
+
// Sprite
var option = new UI.Row();
diff --git a/editor/js/Sidebar.Geometry.LatheGeometry.js b/editor/js/Sidebar.Geometry.LatheGeometry.js
new file mode 100644
index 0000000000000000000000000000000000000000..ba16eb0038cff0e4ade8f6e28bc8782a3af25ca6
--- /dev/null
+++ b/editor/js/Sidebar.Geometry.LatheGeometry.js
@@ -0,0 +1,134 @@
+/**
+ * @author rfm1201
+ */
+
+Sidebar.Geometry.LatheGeometry = function( editor, object ) {
+
+ var signals = editor.signals;
+
+ var container = new UI.Row();
+
+ var parameters = object.geometry.parameters;
+
+ // segments
+
+ var segmentsRow = new UI.Row();
+ var segments = new UI.Integer( parameters.segments ).onChange( update );
+
+ segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
+ segmentsRow.add( segments );
+
+ container.add( segmentsRow );
+
+ // phiStart
+
+ var phiStartRow = new UI.Row();
+ var phiStart = new UI.Number( parameters.phiStart * 180 / Math.PI ).onChange( update );
+
+ phiStartRow.add( new UI.Text( 'Phi start (°)' ).setWidth( '90px' ) );
+ phiStartRow.add( phiStart );
+
+ container.add( phiStartRow );
+
+ // phiLength
+
+ var phiLengthRow = new UI.Row();
+ var phiLength = new UI.Number( parameters.phiLength * 180 / Math.PI ).onChange( update );
+
+ phiLengthRow.add( new UI.Text( 'Phi length (°)' ).setWidth( '90px' ) );
+ phiLengthRow.add( phiLength );
+
+ container.add( phiLengthRow );
+
+ // points
+
+ var lastPointIdx = 0;
+ var pointsUI = [];
+
+ var pointsDiv = new UI.Div();
+ var point;
+ for ( var i = 0; i < parameters.points.length; i ++ ) {
+
+ point = parameters.points[ i ];
+ pointsDiv.add( createPointRow( point.x, point.z ) );
+
+ }
+
+ var pointsRow = new UI.Row().setDisplay( 'flex' );
+
+ var btnAdd = new UI.Button( '+' ).setMarginRight( '15px' ).onClick( function() {
+
+ pointsDiv.add( createPointRow( 0, 0 ) );
+ update();
+
+ } );
+
+ pointsRow.add( new UI.Text( 'Points' ).setWidth( '50px' ), btnAdd, pointsDiv );
+ container.add( pointsRow );
+
+ //
+
+ function createPointRow( x, y ) {
+
+ var pointRow = new UI.Row();
+ var lbl = new UI.Text( lastPointIdx + 1 ).setWidth( '20px' );
+ var txtX = new UI.Number( x ).setRange( 0, Infinity ).setWidth( '40px' ).onChange( update );
+ var txtY = new UI.Number( y ).setWidth( '40px' ).onChange( update );
+ var idx = lastPointIdx;
+ var btn = new UI.Button( '-' ).onClick( function() {
+
+ deletePointRow( idx );
+
+ } );
+
+ pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
+ lastPointIdx ++;
+ pointRow.add( lbl, txtX, txtY, btn );
+
+ return pointRow;
+
+ }
+
+ function deletePointRow( idx ) {
+
+ if ( ! pointsUI[ idx ] ) return;
+
+ pointsDiv.remove( pointsUI[ idx ].row );
+ pointsUI[ idx ] = null;
+
+ update();
+
+ }
+
+ function update() {
+
+ var points = [];
+ var count = 0;
+ var pointUI;
+ for ( var i = 0; i < pointsUI.length; i ++ ) {
+
+ pointUI = pointsUI[ i ];
+ if ( ! pointUI ) {
+
+ continue;
+
+ }
+
+ points.push( new THREE.Vector3( pointUI.x.getValue(), 0, pointUI.y.getValue() ) );
+ count ++;
+ pointUI.lbl.setValue( count );
+
+ }
+
+ editor.execute( new SetGeometryCommand( object, new THREE.LatheGeometry(
+ points,
+ segments.getValue(),
+ phiStart.getValue() / 180 * Math.PI,
+ phiLength.getValue() / 180 * Math.PI
+ ) ) );
+
+ }
+
+ return container;
+
+}