1+
2+
3+ #include " threepp/extras/curves/CatmullRomCurve3.hpp"
4+ #include < threepp/controls/OrbitControls.hpp>
5+ #include < threepp/controls/TransformControls.hpp>
6+ #include < threepp/threepp.hpp>
7+
8+ #include < map>
9+
10+ using namespace threepp ;
11+
12+ int main () {
13+
14+ // Renderer & canvas
15+ Canvas canvas (" Spline Editor" , {{" aa" , 4 }});
16+ GLRenderer renderer (canvas.size ());
17+ renderer.shadowMap ().enabled = true ;
18+
19+ // Scene
20+ auto scene = Scene::create ();
21+ scene->background = Color ().setHex (0xf0f0f0 );
22+
23+ // Camera
24+ auto camera = PerspectiveCamera::create (70 , canvas.aspect (), 1 , 10000 );
25+ camera->position .set (0 , 250 , 1000 );
26+ scene->add (camera);
27+
28+ // Lights
29+ scene->add (AmbientLight::create (0xf0f0f0 , 3 .f ));
30+ auto light = SpotLight::create (0xffffff , 4 .5f );
31+ light->position .set (0 , 1500 , 200 );
32+ light->angle = math::PI * 0 .2f ;
33+ light->decay = 0 ;
34+ light->castShadow = true ;
35+ scene->add (light);
36+
37+ // Ground
38+ auto planeGeo = PlaneGeometry::create (2000 , 2000 );
39+ planeGeo->rotateX (-math::PI / 2 );
40+ auto planeMat = ShadowMaterial::create ();
41+ planeMat->opacity = 0 .2f ;
42+ auto plane = Mesh::create (planeGeo, planeMat);
43+ plane->position .y = -200 ;
44+ plane->receiveShadow = true ;
45+ scene->add (plane);
46+
47+ // Grid helper
48+ auto grid = GridHelper::create (2000 , 100 );
49+ grid->position .y = -199 ;
50+ grid->material ()->opacity = 0.25 ;
51+ grid->material ()->transparent = true ;
52+ scene->add (grid);
53+
54+ // Spline points
55+ std::vector<Vector3> positions = {
56+ {289 .768f , 452 .515f , 56 .1f },
57+ {-53 .563f , 171 .497f , -14 .495f },
58+ {-91 .401f , 176 .431f , -6 .958f },
59+ {-383 .785f , 491 .137f , 47 .869f }};
60+
61+ constexpr int ARC_SEGMENTS = 200 ;
62+
63+ // Spline meshes
64+ std::map<std::string, std::shared_ptr<Line>> splines;
65+
66+ auto createSplineLine = [&](const std::string& type, const Color& color) {
67+ auto curve = std::make_unique<CatmullRomCurve3>(positions);
68+ if (type == " centripetal" ) curve->curveType = CatmullRomCurve3::CurveType::catmullrom;
69+ if (type == " chordal" ) curve->curveType = CatmullRomCurve3::CurveType::chordal;
70+
71+ auto geom = BufferGeometry::create ();
72+ geom->setAttribute (" position" , FloatBufferAttribute::create (std::vector<float >(ARC_SEGMENTS * 3 ), 3 ));
73+ auto line = Line::create (geom, LineBasicMaterial::create ({{" color" , color}}));
74+ line->castShadow = true ;
75+ splines[type] = line;
76+ scene->add (line);
77+ return curve;
78+ };
79+
80+ auto uniformCurve = createSplineLine (" uniform" , Color::red);
81+ auto centripetalCurve = createSplineLine (" centripetal" , Color::green);
82+ auto chordalCurve = createSplineLine (" chordal" , Color::blue);
83+
84+ // Helper objects (control points)
85+ auto boxGeo = BoxGeometry::create (20 , 20 , 20 );
86+ std::vector<Object3D*> splineHelpers;
87+ for (auto & pos : positions) {
88+ auto mat = MeshLambertMaterial::create ({{" color" , Color ().randomize ()}});
89+ auto obj = Mesh::create (boxGeo, mat);
90+ obj->position .copy (pos);
91+ obj->castShadow = true ;
92+ obj->receiveShadow = true ;
93+ scene->add (obj);
94+ splineHelpers.push_back (obj.get ());
95+ }
96+
97+ // Orbit controls
98+ OrbitControls controls (*camera, canvas);
99+ controls.dampingFactor = 0 .2f ;
100+
101+ auto updateSplines ([&] {
102+ // Update spline meshes whenever a control point moves
103+ auto updateSplineMesh = [&](const CatmullRomCurve3& curve, const std::shared_ptr<Line>& line) {
104+ auto posAttr = line->geometry ()->getAttribute <float >(" position" );
105+ for (int i = 0 ; i < ARC_SEGMENTS; i++) {
106+ float t = static_cast <float >(i) / static_cast <float >(ARC_SEGMENTS - 1 );
107+ Vector3 p;
108+ curve.getPoint (t, p);
109+ posAttr->setXYZ (i, p.x , p.y , p.z );
110+ }
111+ posAttr->needsUpdate ();
112+ };
113+
114+ // Copy helper positions to curve
115+ for (int i = 0 ; i < splineHelpers.size (); i++) {
116+ positions[i] = splineHelpers[i]->position ;
117+ }
118+ uniformCurve->points = positions;
119+ centripetalCurve->points = positions;
120+ chordalCurve->points = positions;
121+
122+ updateSplineMesh (*uniformCurve, splines[" uniform" ]);
123+ updateSplineMesh (*centripetalCurve, splines[" centripetal" ]);
124+ updateSplineMesh (*chordalCurve, splines[" chordal" ]);
125+ });
126+
127+ LambdaEventListener eventListener ([&](const Event&) {
128+ updateSplines ();
129+ });
130+
131+
132+ LambdaEventListener changeListener ([&](const Event& event) {
133+ controls.enabled = !std::any_cast<bool >(event.target );
134+ });
135+
136+ // Transform controls
137+ TransformControls transformControl (*camera, canvas);
138+ transformControl.addEventListener (" change" , eventListener);
139+ transformControl.addEventListener (" dragging-changed" , changeListener);
140+ scene->add (transformControl);
141+
142+
143+ Vector2 mouse{-Infinity<float >, -Infinity<float >};
144+ MouseDownListener mouseListener ([&](int button, const Vector2& pos) {
145+ if (button == 0 ) {
146+
147+ Raycaster raycaster;
148+ const auto size = canvas.size ();
149+ mouse.x = (pos.x / static_cast <float >(size.width ())) * 2 - 1 ;
150+ mouse.y = -(pos.y / static_cast <float >(size.height ())) * 2 + 1 ;
151+ raycaster.setFromCamera (mouse, *camera);
152+ const auto intersects = raycaster.intersectObjects (splineHelpers, false );
153+ if (!intersects.empty ()) {
154+ transformControl.attach (*intersects[0 ].object );
155+ }
156+ } else if (button == 1 ) {
157+ transformControl.detach ();
158+ }
159+ });
160+
161+ canvas.addMouseListener (mouseListener);
162+
163+ updateSplines ();
164+ canvas.animate ([&] {
165+ controls.update ();
166+ renderer.render (*scene, *camera);
167+ });
168+ }
0 commit comments