@dcl-regenesislabs/opendcl 0.2.1-26165320302.commit-e6effe4 → 0.2.1-26238928766.commit-28648d7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +5 -3
  2. package/context/sdk7-cheat-sheet.md +4 -0
  3. package/dist/index.js +0 -12
  4. package/dist/index.js.map +1 -1
  5. package/extensions/dcl-init.ts +58 -6
  6. package/package.json +3 -3
  7. package/prompts/system.md +71 -41
  8. package/skills/add-3d-models/SKILL.md +120 -70
  9. package/skills/add-interactivity/SKILL.md +74 -2
  10. package/skills/advanced-input/SKILL.md +34 -1
  11. package/skills/advanced-rendering/SKILL.md +82 -9
  12. package/skills/animations-tweens/SKILL.md +203 -98
  13. package/skills/audio-analysis/SKILL.md +164 -0
  14. package/skills/audio-video/SKILL.md +184 -83
  15. package/skills/build-ui/SKILL.md +25 -2
  16. package/skills/camera-control/SKILL.md +78 -7
  17. package/skills/create-scene/SKILL.md +56 -13
  18. package/skills/deploy-scene/SKILL.md +12 -0
  19. package/skills/deploy-worlds/SKILL.md +35 -0
  20. package/skills/editor-gizmo/.gitignore +11 -0
  21. package/skills/editor-gizmo/SKILL.md +222 -0
  22. package/skills/editor-gizmo/src/__editor/camera.ts +277 -0
  23. package/skills/editor-gizmo/src/__editor/discovery.ts +186 -0
  24. package/skills/editor-gizmo/src/__editor/drag.ts +265 -0
  25. package/skills/editor-gizmo/src/__editor/gizmo.ts +496 -0
  26. package/skills/editor-gizmo/src/__editor/history.ts +72 -0
  27. package/skills/editor-gizmo/src/__editor/index.ts +137 -0
  28. package/skills/editor-gizmo/src/__editor/input.ts +55 -0
  29. package/skills/editor-gizmo/src/__editor/math-utils.ts +114 -0
  30. package/skills/editor-gizmo/src/__editor/persistence.ts +113 -0
  31. package/skills/editor-gizmo/src/__editor/selection.ts +157 -0
  32. package/skills/editor-gizmo/src/__editor/state.ts +117 -0
  33. package/skills/editor-gizmo/src/__editor/ui.tsx +697 -0
  34. package/skills/game-design/SKILL.md +1 -2
  35. package/skills/lighting-environment/SKILL.md +103 -56
  36. package/skills/multiplayer-sync/SKILL.md +31 -2
  37. package/skills/nft-blockchain/SKILL.md +45 -40
  38. package/skills/npcs/SKILL.md +180 -0
  39. package/skills/optimize-scene/SKILL.md +7 -2
  40. package/skills/particle-system/SKILL.md +222 -0
  41. package/skills/player-avatar/SKILL.md +133 -7
  42. package/skills/player-physics/SKILL.md +93 -0
  43. package/skills/scene-runtime/SKILL.md +9 -5
  44. package/skills/visual-feedback/SKILL.md +1 -0
  45. package/extensions/dcl-setup-ollama.ts +0 -312
@@ -0,0 +1,265 @@
1
+ /** Ray-plane intersection drag system for translate and rotate. */
2
+
3
+ import {
4
+ engine,
5
+ Entity,
6
+ Transform,
7
+ inputSystem,
8
+ InputAction,
9
+ PointerEventType,
10
+ PrimaryPointerInfo,
11
+ } from '@dcl/sdk/ecs'
12
+ import { Vector3, Quaternion } from '@dcl/sdk/math'
13
+ import { Axis, MAX_PARENT_DEPTH, state, selectableInfoMap, handleAxisMap, handleDiscMap, handleArrowMap } from './state'
14
+ import { axisToVector, getDragPlaneNormal, rayPlaneIntersect, hitAngleOnPlane, hitAngleOnWorldPlane, copyVec3, copyQuat, getOtherAxes } from './math-utils'
15
+ import { getActiveCameraTransform, lockCamera, unlockCamera } from './camera'
16
+ import { getGizmoCenter, getParentWorldRotation, setArrowMaterial, setRingMaterial } from './gizmo'
17
+ import { sendEntityUpdate } from './persistence'
18
+ import { captureTransform, pushHistory, TransformSnapshot } from './history'
19
+
20
+ /**
21
+ * Convert a world-space displacement vector to local space,
22
+ * accounting for parent rotation and cumulative scale chain.
23
+ * Returns the local displacement vector to add to local position.
24
+ */
25
+ function worldToLocalDelta(entity: Entity, worldDelta: Vector3): Vector3 {
26
+ // Rotate world delta into local space using inverse parent rotation
27
+ const parentRot = getParentWorldRotation(entity)
28
+ const invParent = Quaternion.create(-parentRot.x, -parentRot.y, -parentRot.z, parentRot.w)
29
+ let local = Vector3.rotate(worldDelta, invParent)
30
+
31
+ // Walk up parent chain and accumulate scale per axis
32
+ const info = selectableInfoMap.get(entity)
33
+ let parentId = info?.parentEntity
34
+ let depth = 0
35
+ let sx = 1, sy = 1, sz = 1
36
+
37
+ while (parentId && depth < MAX_PARENT_DEPTH) {
38
+ const pe = parentId as Entity
39
+ if (!Transform.has(pe)) break
40
+ const pt = Transform.get(pe)
41
+ sx *= pt.scale.x; sy *= pt.scale.y; sz *= pt.scale.z
42
+ const parentInfo = selectableInfoMap.get(pe)
43
+ parentId = parentInfo?.parentEntity
44
+ depth++
45
+ }
46
+
47
+ return Vector3.create(
48
+ sx !== 0 ? local.x / sx : local.x,
49
+ sy !== 0 ? local.y / sy : local.y,
50
+ sz !== 0 ? local.z / sz : local.z,
51
+ )
52
+ }
53
+
54
+ let dragBeforeSnapshot: TransformSnapshot | undefined
55
+ /** World-space axis direction for single-axis translate drag (parent-rotated). */
56
+ let dragWorldAxis: Vector3 = Vector3.Right()
57
+ /** World-space rotation axis for rotate drag. */
58
+ let dragRotWorldAxis: Vector3 = Vector3.Up()
59
+ /** Parent world rotation at drag start (for converting world rot back to local). */
60
+ let dragParentWorldRot: { x: number; y: number; z: number; w: number } = Quaternion.Identity()
61
+ /** World-space local axes for plane drag (parent-rotated). */
62
+ let dragLocalAxes: { a1: Axis; d1: Vector3; a2: Axis; d2: Vector3 } = {
63
+ a1: 'x', d1: Vector3.Right(), a2: 'z', d2: Vector3.Forward()
64
+ }
65
+
66
+ /**
67
+ * Start a plane-constrained drag. normalAxis is the axis perpendicular to the plane:
68
+ * - normalAxis 'y' → drag on XZ plane (horizontal movement)
69
+ * - normalAxis 'z' → drag on XY plane
70
+ * - normalAxis 'x' → drag on YZ plane
71
+ */
72
+ export function startPlaneDrag(normalAxis: Axis) {
73
+ if (state.selectedEntity === undefined || !Transform.has(state.selectedEntity)) return
74
+
75
+ dragBeforeSnapshot = captureTransform(state.selectedEntity)
76
+
77
+ const entityPos = Transform.get(state.selectedEntity).position
78
+ const gizmoCenter = getGizmoCenter(state.selectedEntity)
79
+ const cameraT = getActiveCameraTransform()
80
+
81
+ const pointer = PrimaryPointerInfo.getOrNull(engine.RootEntity)
82
+ if (!pointer || !pointer.worldRayDirection) return
83
+
84
+ // World-aligned plane normal (gizmos are world-aligned)
85
+ const worldNormal = axisToVector(normalAxis)
86
+
87
+ const hit = rayPlaneIntersect(cameraT.position, pointer.worldRayDirection, gizmoCenter, worldNormal)
88
+ if (!hit) return
89
+
90
+ // Determine the two world axes that lie in the plane
91
+ const axes = getOtherAxes(normalAxis)
92
+ dragLocalAxes = {
93
+ a1: axes[0], d1: axisToVector(axes[0]),
94
+ a2: axes[1], d2: axisToVector(axes[1]),
95
+ }
96
+
97
+ state.isDragging = true
98
+ state.dragPlaneMode = normalAxis
99
+ state.dragAxis = 'x' // unused, needs a value
100
+ state.dragStartPos = Vector3.create(entityPos.x, entityPos.y, entityPos.z)
101
+ state.dragStartWorldPos = Vector3.create(gizmoCenter.x, gizmoCenter.y, gizmoCenter.z)
102
+ state.dragStartHit = hit
103
+ state.dragPlaneNormal = Vector3.create(worldNormal.x, worldNormal.y, worldNormal.z)
104
+
105
+ lockCamera()
106
+ }
107
+
108
+ export function startDrag(axis: Axis) {
109
+ if (state.selectedEntity === undefined || !Transform.has(state.selectedEntity)) return
110
+
111
+ // Capture transform before any changes for undo
112
+ dragBeforeSnapshot = captureTransform(state.selectedEntity)
113
+
114
+ const entityPos = Transform.get(state.selectedEntity).position
115
+ const gizmoCenter = getGizmoCenter(state.selectedEntity)
116
+ const cameraT = getActiveCameraTransform()
117
+ const cameraForward = Vector3.rotate(Vector3.Forward(), cameraT.rotation)
118
+
119
+ const pointer = PrimaryPointerInfo.getOrNull(engine.RootEntity)
120
+ if (!pointer || !pointer.worldRayDirection) return
121
+
122
+ if (state.gizmoMode === 'translate') {
123
+ // World-aligned arrows: drag along world axes directly
124
+ dragWorldAxis = axisToVector(axis)
125
+
126
+ const planeNormal = getDragPlaneNormal(axis, cameraForward, dragWorldAxis)
127
+ // Use world position (gizmoCenter) for plane intersection, but track local pos for delta
128
+ const hit = rayPlaneIntersect(cameraT.position, pointer.worldRayDirection, gizmoCenter, planeNormal)
129
+ if (!hit) return
130
+
131
+ state.isDragging = true
132
+ state.dragPlaneMode = undefined
133
+ state.dragAxis = axis
134
+ state.dragStartPos = Vector3.create(entityPos.x, entityPos.y, entityPos.z)
135
+ state.dragStartWorldPos = Vector3.create(gizmoCenter.x, gizmoCenter.y, gizmoCenter.z)
136
+ state.dragStartHit = hit
137
+ state.dragPlaneNormal = Vector3.create(planeNormal.x, planeNormal.y, planeNormal.z)
138
+ } else {
139
+ const center = getGizmoCenter(state.selectedEntity)
140
+ // World-aligned rotation: rings always point along world X, Y, Z
141
+ const parentRot = getParentWorldRotation(state.selectedEntity)
142
+ const worldAxis = axisToVector(axis)
143
+
144
+ const hit = rayPlaneIntersect(cameraT.position, pointer.worldRayDirection, center, worldAxis)
145
+ if (!hit) return
146
+
147
+ dragRotWorldAxis = worldAxis
148
+ dragParentWorldRot = Quaternion.create(parentRot.x, parentRot.y, parentRot.z, parentRot.w)
149
+
150
+ state.isDragging = true
151
+ state.dragAxis = axis
152
+ state.dragPlaneNormal = Vector3.create(worldAxis.x, worldAxis.y, worldAxis.z)
153
+ state.dragRotCenter = Vector3.create(center.x, center.y, center.z)
154
+ const entRot = Transform.get(state.selectedEntity).rotation
155
+ state.dragStartRot = Quaternion.create(entRot.x, entRot.y, entRot.z, entRot.w)
156
+ state.dragStartAngle = hitAngleOnWorldPlane(hit, center, worldAxis)
157
+ }
158
+
159
+ lockCamera()
160
+ }
161
+
162
+ export function dragSystem(_dt: number) {
163
+ if (!state.editorActive) return
164
+ if (!state.isDragging || state.selectedEntity === undefined || !Transform.has(state.selectedEntity))
165
+ return
166
+
167
+ if (inputSystem.isTriggered(InputAction.IA_POINTER, PointerEventType.PET_UP)) { endDrag(); return }
168
+ if (!inputSystem.isPressed(InputAction.IA_POINTER)) { endDrag(); return }
169
+
170
+ const pointer = PrimaryPointerInfo.getOrNull(engine.RootEntity)
171
+ if (!pointer || !pointer.worldRayDirection) return
172
+ const cameraT = getActiveCameraTransform()
173
+
174
+ if (state.gizmoMode === 'translate') {
175
+ // Intersect on the world-space plane (using world position, not local)
176
+ const hit = rayPlaneIntersect(cameraT.position, pointer.worldRayDirection, state.dragStartWorldPos, state.dragPlaneNormal)
177
+ if (!hit) return
178
+
179
+ const worldDelta = Vector3.subtract(hit, state.dragStartHit)
180
+
181
+ let constrainedWorld: Vector3
182
+ if (state.dragPlaneMode !== undefined) {
183
+ // Plane drag: keep only the 2 world axes that lie in the plane
184
+ const comp1 = Vector3.dot(worldDelta, dragLocalAxes.d1)
185
+ const comp2 = Vector3.dot(worldDelta, dragLocalAxes.d2)
186
+ constrainedWorld = Vector3.add(
187
+ Vector3.scale(dragLocalAxes.d1, comp1),
188
+ Vector3.scale(dragLocalAxes.d2, comp2),
189
+ )
190
+ } else {
191
+ // Single axis: project onto world axis, convert to local
192
+ const worldDisplacement = Vector3.dot(worldDelta, dragWorldAxis)
193
+ constrainedWorld = Vector3.scale(dragWorldAxis, worldDisplacement)
194
+ }
195
+
196
+ const localDelta = worldToLocalDelta(state.selectedEntity, constrainedWorld)
197
+ const t = Transform.getMutable(state.selectedEntity)
198
+ t.position.x = state.dragStartPos.x + localDelta.x
199
+ t.position.y = state.dragStartPos.y + localDelta.y
200
+ t.position.z = state.dragStartPos.z + localDelta.z
201
+ } else {
202
+ const hit = rayPlaneIntersect(cameraT.position, pointer.worldRayDirection, state.dragRotCenter, state.dragPlaneNormal)
203
+ if (!hit) return
204
+
205
+ const currentAngle = hitAngleOnWorldPlane(hit, state.dragRotCenter, dragRotWorldAxis)
206
+ const degrees = (currentAngle - state.dragStartAngle) * (180 / Math.PI)
207
+
208
+ // Compute incremental rotation in world space, then convert to local
209
+ const worldIncremental = Quaternion.fromAngleAxis(degrees, dragRotWorldAxis)
210
+ // localIncremental = inv(parentWorldRot) * worldIncremental * parentWorldRot
211
+ // For unit quaternions: inverse = conjugate (negate xyz, keep w)
212
+ const ip = dragParentWorldRot
213
+ const invParent = Quaternion.create(-ip.x, -ip.y, -ip.z, ip.w)
214
+ const localIncremental = Quaternion.multiply(
215
+ Quaternion.multiply(invParent, worldIncremental),
216
+ dragParentWorldRot
217
+ )
218
+ const newRot = Quaternion.multiply(localIncremental, state.dragStartRot)
219
+
220
+ const t = Transform.getMutable(state.selectedEntity)
221
+ copyQuat(t.rotation, newRot)
222
+ }
223
+ }
224
+
225
+ function endDrag() {
226
+ if (!state.isDragging) return
227
+ state.isDragging = false
228
+ state.dragPlaneMode = undefined
229
+ unlockCamera()
230
+
231
+ // Restore gizmo visuals
232
+ if (state.gizmoMode === 'rotate') {
233
+ for (const [h] of handleDiscMap) {
234
+ const a = handleAxisMap.get(h)
235
+ if (a) setRingMaterial(h, a, false)
236
+ }
237
+ } else {
238
+ for (const [h, parts] of handleArrowMap) {
239
+ const a = handleAxisMap.get(h)
240
+ if (!a) continue
241
+ for (const p of parts) setArrowMaterial(p, a, false)
242
+ }
243
+ }
244
+
245
+ // Send update, push to history, log
246
+ if (state.selectedEntity !== undefined && Transform.has(state.selectedEntity)) {
247
+ const afterSnapshot = captureTransform(state.selectedEntity)
248
+
249
+ if (dragBeforeSnapshot) {
250
+ pushHistory(state.selectedEntity, dragBeforeSnapshot, afterSnapshot)
251
+ dragBeforeSnapshot = undefined
252
+ }
253
+
254
+ sendEntityUpdate(state.selectedEntity)
255
+
256
+ const t = Transform.get(state.selectedEntity)
257
+ if (state.gizmoMode === 'translate') {
258
+ const label = state.dragPlaneMode !== undefined ? `plane(${state.dragPlaneMode})` : state.dragAxis
259
+ console.log(`[editor] move ${label}: pos=(${t.position.x.toFixed(2)}, ${t.position.y.toFixed(2)}, ${t.position.z.toFixed(2)})`)
260
+ } else {
261
+ const euler = Quaternion.toEulerAngles(t.rotation)
262
+ console.log(`[editor] rotate ${state.dragAxis}: rot=(${euler.x.toFixed(1)}, ${euler.y.toFixed(1)}, ${euler.z.toFixed(1)})`)
263
+ }
264
+ }
265
+ }