0

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí