Xây dựng 3D Editor đầu tiên với Three.js — Select, Drag, Rotate, Scale
Mở đầu
Sau khi hiểu:
- scene
- camera
- mesh
- coordinate system
… bước tiếp theo sẽ là:
biến scene 3D thành một editor thật sự.
Đây là giai đoạn:
- thú vị nhất
- nhưng cũng khiến nhiều developer “vỡ đầu” nhất.
Bởi vì từ đây:
bạn không còn chỉ “render object”.
Mà đang bắt đầu xây dựng:
- interaction system
- transform system
- editor architecture
- user experience cho môi trường 3D
Đây chính là phần khiến:
- Blender
- Unity
- Figma
- Maya
trở thành:
những công cụ cực kỳ phức tạp.
Và điều thú vị là:
Three.js cho phép chúng ta build gần như mọi thứ đó bằng JavaScript.
Trong bài này, chúng ta sẽ đi qua:
- object selection
- raycasting
- drag system
- rotate
- scale
- snapping
- transform controls
- state management cơ bản
Đây chính là:
nền móng của mọi 3D editor thực tế.
1. Tư duy quan trọng nhất của editor
Khi mới học Three.js, rất nhiều người nghĩ:
“Editor = render object + drag object.”
Nhưng thực tế:
phần khó nhất của editor không phải rendering.
Mà là:
quản lý trạng thái (state).
2. Editor thật sự là gì?
Editor thực chất là:
hệ thống quản lý tương tác giữa người dùng và object.
Ví dụ:
- object nào đang được chọn?
- object nào đang hover?
- object nào đang drag?
- object nào đang bị lock?
- transform đang local hay world?
- đang rotate hay move?
- snapping có bật không?
- object có collision không?
Đây mới là:
phần khó thật sự của editor.
3. Vì sao editor 3D khó hơn editor 2D?
Trong editor 2D:
- bạn làm việc với:
- x
- y
Nhưng trong 3D:
- bạn phải xử lý:
- x
- y
- z
- camera
- perspective
- depth
- raycasting
- local/world transform
Ví dụ:
- object A nằm trước object B
- nhưng trên màn hình:
- chúng overlap
Lúc này:
click chuột phải xác định object nào đang ở phía trước.
Đây là lý do:
raycasting tồn tại.
4. Raycasting — nền tảng của mọi interaction
Muốn:
- click object
- hover object
- drag object
- select object
… bạn gần như luôn phải dùng:
Raycaster.
5. Raycaster hoạt động như thế nào?
Hãy tưởng tượng:
- chuột của bạn bắn ra một tia laser
- tia đó đi xuyên vào thế giới 3D
- object nào bị tia chạm đầu tiên: → object được chọn
Đó chính là:
raycasting.
6. Khởi tạo Raycaster
const raycaster = new THREE.Raycaster()
7. Vì sao cần convert tọa độ chuột?
Mouse trong browser dùng:
0 → width
0 → height
Nhưng Three.js dùng:
-1 → 1
Đây gọi là:
NDC (Normalized Device Coordinates)
Convert mouse
mouse.x = (event.clientX / width) * 2 - 1
mouse.y = -(event.clientY / height) * 2 + 1
Vì sao Y phải đảo dấu?
Trong browser:
- Y tăng xuống dưới
Trong WebGL:
- Y tăng lên trên
Đây là chỗ beginner thường bug.
8. Bắn ray từ camera
raycaster.setFromCamera(mouse, camera)
Điều này nghĩa là:
tạo tia từ camera đi qua vị trí chuột.
9. Detect object
const intersects = raycaster.intersectObjects(objects)
Nếu:
intersects.length > 0
→ có object được hit.
10. Vì sao Raycasting quan trọng đến vậy?
Bởi vì:
gần như mọi interaction trong editor đều dựa vào raycasting.
Ví dụ:
- click select
- hover outline
- drag object
- surface snapping
- measurement tool
- terrain placement
11. Selection system — trái tim của editor
Mọi editor đều cần:
selectedObject
Ví dụ:
let selectedObject = null
12. Selection nghe đơn giản nhưng thực tế rất khó
Ban đầu:
selected = cube
Nghe rất đơn giản.
Nhưng editor thật sự sẽ có:
- single select
- multi select
- group select
- hierarchy select
- hidden object
- locked object
- outline effect
- gizmo attach
- deselect logic
Đây là lý do:
selection system thường là nền tảng của editor architecture.
13. Visual feedback cực kỳ quan trọng
Khi object được select:
- user phải biết ngay.
Ví dụ:
- đổi màu
- outline
- glow
- bounding box
- gizmo xuất hiện
Nếu không:
editor sẽ rất khó dùng.
14. TransformControls — vũ khí mạnh nhất của Three.js
Three.js có sẵn:
TransformControls
Đây là:
gizmo giống Blender/Unity.
Nó cho phép:
- move
- rotate
- scale
15. Attach object vào TransformControls
transformControls.attach(mesh)
Khi attach:
- gizmo sẽ xuất hiện
- object có thể transform
16. Chuyển mode
transformControls.setMode('translate')
Các mode gồm:
| Mode | Ý nghĩa |
|---|---|
| translate | move |
| rotate | xoay |
| scale | scale |
17. Vì sao TransformControls rất mạnh?
Bởi vì nó xử lý:
- axis math
- drag interaction
- rotation math
- transform visualization
Đây là phần cực kỳ khó nếu tự build.
18. Local space vs World space
Đây là thứ khiến:
rất nhiều developer bị “loạn não”.
19. World space là gì?
World space:
transform theo trục thế giới.
Ví dụ:
- X luôn là trái/phải của scene
Dù object có xoay: → trục world không đổi.
20. Local space là gì?
Local space:
transform theo hướng của object.
Ví dụ:
- object quay 90 độ
- move local X
→ object sẽ đi theo hướng đã xoay.
21. Đây là phần khiến editor khó
Ví dụ:
- parent rotate
- child move local
Lúc này:
- direction không còn đơn giản nữa.
Đây là nơi:
matrix transformation bắt đầu xuất hiện.
22. Drag system thật sự hoạt động thế nào?
Nhiều beginner nghĩ:
“drag = update position.”
Sai hoàn toàn.
Drag system thật sự thường cần:
- raycasting
- intersection plane
- offset
- snapping
- collision
- transform conversion
23. Workflow drag phổ biến
Mouse down
- select object
- lưu offset
- start dragging
Mouse move
- raycast xuống plane
- tính intersection point
- update position
Mouse up
- stop dragging
24. Vì sao cần plane?
Ví dụ:
- object đang nằm trên mặt đất
Khi drag:
- ta thường raycast xuống:
- ground plane
Để biết:
object nên đặt ở đâu.
25. Offset là gì?
Nếu không lưu offset:
- object sẽ “teleport” vào giữa chuột.
Offset giúp:
drag mượt và tự nhiên hơn.
26. Snapping — cảm giác “professional”
Một editor không snapping:
thường có cảm giác rất nghiệp dư.
27. Grid snapping
Ví dụ:
position.x = Math.round(position.x)
→ snap theo grid 1 unit.
28. Angle snapping
Ví dụ:
- rotate mỗi:
- 15°
- 45°
- 90°
Điều này khiến:
- placement chính xác hơn
- UX tốt hơn rất nhiều
29. Surface snapping
Ví dụ:
- kéo object lên tường
- object tự dính vào bề mặt
Đây là kỹ thuật:
cực kỳ phổ biến trong editor thực tế.
30. Vì sao editor 3D khó?
Bởi vì mọi thứ liên quan đến:
- matrix
- coordinate conversion
- hierarchy
- transform propagation
Ví dụ:
- parent scale
- child rotate
- local/world conversion
Đây là phần:
khiến rất nhiều beginner bị “ngợp”.
31. Bounding box cực kỳ quan trọng
Editor thực tế thường cần:
- detect overlap
- selection box
- collision
- snapping
Ví dụ:
const box = new THREE.Box3()
32. Vấn đề của rotated bounding box
Khi object rotate:
- bounding box axis-aligned → không còn chính xác tuyệt đối.
Đây là nơi:
OBB (Oriented Bounding Box) xuất hiện.
33. State architecture nên như nào?
Sai lầm lớn:
để logic khắp nơi.
Ví dụ:
- selection nằm component A
- transform nằm component B
- history nằm component C
Sau vài tháng:
project sẽ rất khó maintain.
34. Nên chia manager riêng
Ví dụ:
EditorStore
SelectionManager
TransformManager
SceneManager
HistoryManager
35. Vì sao manager pattern quan trọng?
Bởi vì editor:
- rất nhiều interaction
- rất nhiều side effects
Nếu không organize tốt:
bug sẽ xuất hiện khắp nơi.
36. Điều quan trọng nhất khi build editor
Đừng cố:
- clean architecture hoàn hảo
- optimization quá sớm
- feature quá lớn
Hãy:
- build interaction trước
- fix UX trước
- tối ưu sau
37. Editor thực chất là UX engineering
Một editor tốt không phải:
editor nhiều feature nhất.
Mà là:
editor dễ dùng nhất.
Ví dụ:
- drag mượt
- snapping chính xác
- camera dễ điều khiển
- selection rõ ràng
Đây mới là:
thứ quyết định trải nghiệm thật sự.
38. Điều mình học được khi build editor 3D
Ban đầu mình nghĩ:
“3D editor chủ yếu là rendering.”
Nhưng càng làm càng nhận ra:
- rendering chỉ là phần nổi
- interaction mới là phần khó nhất
Đặc biệt là:
- transform math
- hierarchy
- coordinate conversion
- snapping
- state synchronization
Kết bài
Trong bài tiếp theo, chúng ta sẽ đi sâu hơn vào:
- editor architecture thực chiến
- undo/redo
- instancing
- optimization
- serialization
- asset pipeline
- performance bottleneck
- production mindset
Đây là phần biến:
demo Three.js
thành:
sản phẩm thật sự có thể chạy production.
All rights reserved