spoint 0.1.505 → 0.1.506
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.
|
@@ -1,267 +1,108 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
server: {
|
|
3
3
|
setup(ctx) {
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
4
|
+
console.log('[matrix-construct-room] server setup')
|
|
5
|
+
const planeSize = 50
|
|
6
|
+
const planeThickness = 0.5
|
|
7
7
|
|
|
8
|
-
// Floor
|
|
8
|
+
// Floor plane with physics
|
|
9
9
|
ctx.physics.addColliderFromConfig({
|
|
10
10
|
type: 'box',
|
|
11
|
-
size: [
|
|
12
|
-
position: [0, -
|
|
11
|
+
size: [planeSize / 2, planeThickness / 2, planeSize / 2],
|
|
12
|
+
position: [0, -planeThickness / 2, 0],
|
|
13
13
|
mass: 0,
|
|
14
14
|
dynamic: false
|
|
15
15
|
})
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
ctx.
|
|
19
|
-
type: 'box',
|
|
20
|
-
size: [roomSize / 2, wallThickness / 2, roomSize / 2],
|
|
21
|
-
position: [0, wallHeight + wallThickness / 2, 0],
|
|
22
|
-
mass: 0,
|
|
23
|
-
dynamic: false
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// Walls - back (negative Z)
|
|
27
|
-
ctx.physics.addColliderFromConfig({
|
|
28
|
-
type: 'box',
|
|
29
|
-
size: [roomSize / 2, wallHeight / 2, wallThickness / 2],
|
|
30
|
-
position: [0, wallHeight / 2, -roomSize / 2],
|
|
31
|
-
mass: 0,
|
|
32
|
-
dynamic: false
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// Walls - front (positive Z)
|
|
36
|
-
ctx.physics.addColliderFromConfig({
|
|
37
|
-
type: 'box',
|
|
38
|
-
size: [roomSize / 2, wallHeight / 2, wallThickness / 2],
|
|
39
|
-
position: [0, wallHeight / 2, roomSize / 2],
|
|
40
|
-
mass: 0,
|
|
41
|
-
dynamic: false
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
// Walls - left (negative X)
|
|
45
|
-
ctx.physics.addColliderFromConfig({
|
|
46
|
-
type: 'box',
|
|
47
|
-
size: [wallThickness / 2, wallHeight / 2, roomSize / 2],
|
|
48
|
-
position: [-roomSize / 2, wallHeight / 2, 0],
|
|
49
|
-
mass: 0,
|
|
50
|
-
dynamic: false
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Walls - right (positive X)
|
|
54
|
-
ctx.physics.addColliderFromConfig({
|
|
55
|
-
type: 'box',
|
|
56
|
-
size: [wallThickness / 2, wallHeight / 2, roomSize / 2],
|
|
57
|
-
position: [roomSize / 2, wallHeight / 2, 0],
|
|
58
|
-
mass: 0,
|
|
59
|
-
dynamic: false
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Store room dimensions for client rendering
|
|
63
|
-
ctx.state.roomSize = roomSize
|
|
64
|
-
ctx.state.wallHeight = wallHeight
|
|
65
|
-
ctx.state.wallThickness = wallThickness
|
|
66
|
-
ctx.state.gridSize = 2
|
|
17
|
+
ctx.state.planeSize = planeSize
|
|
18
|
+
ctx.state.planeThickness = planeThickness
|
|
67
19
|
}
|
|
68
20
|
},
|
|
69
21
|
client: {
|
|
70
22
|
setup(engine) {
|
|
71
|
-
|
|
72
|
-
this.
|
|
23
|
+
console.log('[matrix-construct-room] client setup')
|
|
24
|
+
this.createConstructPlane(engine)
|
|
73
25
|
},
|
|
74
26
|
render(ctx) {
|
|
75
27
|
return {
|
|
76
28
|
position: [0, 0, 0],
|
|
77
29
|
rotation: [0, 0, 0, 1],
|
|
78
|
-
custom: {
|
|
79
|
-
// Empty - we create the room in setup
|
|
80
|
-
}
|
|
30
|
+
custom: {}
|
|
81
31
|
}
|
|
82
32
|
},
|
|
83
|
-
|
|
33
|
+
createConstructPlane(engine) {
|
|
84
34
|
const { THREE, scene } = engine
|
|
85
|
-
const
|
|
86
|
-
const wallHeight = 8
|
|
87
|
-
const wallThickness = 0.5
|
|
35
|
+
const planeSize = 50
|
|
88
36
|
const gridSize = 2
|
|
89
37
|
|
|
90
|
-
|
|
91
|
-
const whiteMaterial = new THREE.MeshStandardMaterial({
|
|
92
|
-
color: 0xffffff,
|
|
93
|
-
roughness: 0.1,
|
|
94
|
-
metalness: 0,
|
|
95
|
-
emissive: 0x222222,
|
|
96
|
-
emissiveIntensity: 0.3,
|
|
97
|
-
side: THREE.DoubleSide
|
|
98
|
-
})
|
|
38
|
+
console.log('[matrix-construct-room] creating plane geometry')
|
|
99
39
|
|
|
100
|
-
//
|
|
101
|
-
const
|
|
102
|
-
color: 0x888888,
|
|
103
|
-
roughness: 0.3,
|
|
104
|
-
metalness: 0,
|
|
105
|
-
emissive: 0x111111,
|
|
106
|
-
emissiveIntensity: 0.1,
|
|
107
|
-
side: THREE.DoubleSide,
|
|
108
|
-
transparent: true,
|
|
109
|
-
opacity: 0.4
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
// Bright grid line material for main axes
|
|
113
|
-
const axisMaterial = new THREE.MeshStandardMaterial({
|
|
40
|
+
// Simple gray plane
|
|
41
|
+
const planeMaterial = new THREE.MeshStandardMaterial({
|
|
114
42
|
color: 0xcccccc,
|
|
115
|
-
roughness: 0.
|
|
43
|
+
roughness: 0.8,
|
|
116
44
|
metalness: 0,
|
|
117
|
-
emissive: 0x333333,
|
|
118
|
-
emissiveIntensity: 0.2,
|
|
119
45
|
side: THREE.DoubleSide
|
|
120
46
|
})
|
|
121
47
|
|
|
122
|
-
//
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// Ceiling
|
|
130
|
-
const ceilGeo = new THREE.BoxGeometry(roomSize, wallThickness, roomSize)
|
|
131
|
-
const ceil = new THREE.Mesh(ceilGeo, whiteMaterial)
|
|
132
|
-
ceil.position.set(0, wallHeight + wallThickness / 2, 0)
|
|
133
|
-
scene.add(ceil)
|
|
134
|
-
|
|
135
|
-
// Walls
|
|
136
|
-
const wallGeo = new THREE.BoxGeometry(roomSize, wallHeight, wallThickness)
|
|
137
|
-
const sideWallGeo = new THREE.BoxGeometry(wallThickness, wallHeight, roomSize)
|
|
138
|
-
|
|
139
|
-
// Back wall (negative Z)
|
|
140
|
-
const backWall = new THREE.Mesh(wallGeo, whiteMaterial)
|
|
141
|
-
backWall.position.set(0, wallHeight / 2, -roomSize / 2)
|
|
142
|
-
backWall.receiveShadow = true
|
|
143
|
-
scene.add(backWall)
|
|
144
|
-
|
|
145
|
-
// Front wall (positive Z)
|
|
146
|
-
const frontWall = new THREE.Mesh(wallGeo, whiteMaterial)
|
|
147
|
-
frontWall.position.set(0, wallHeight / 2, roomSize / 2)
|
|
148
|
-
frontWall.receiveShadow = true
|
|
149
|
-
scene.add(frontWall)
|
|
150
|
-
|
|
151
|
-
// Left wall (negative X)
|
|
152
|
-
const leftWall = new THREE.Mesh(sideWallGeo, whiteMaterial)
|
|
153
|
-
leftWall.position.set(-roomSize / 2, wallHeight / 2, 0)
|
|
154
|
-
leftWall.receiveShadow = true
|
|
155
|
-
scene.add(leftWall)
|
|
156
|
-
|
|
157
|
-
// Right wall (positive X)
|
|
158
|
-
const rightWall = new THREE.Mesh(sideWallGeo, whiteMaterial)
|
|
159
|
-
rightWall.position.set(roomSize / 2, wallHeight / 2, 0)
|
|
160
|
-
rightWall.receiveShadow = true
|
|
161
|
-
scene.add(rightWall)
|
|
48
|
+
// Main plane
|
|
49
|
+
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize)
|
|
50
|
+
const plane = new THREE.Mesh(planeGeo, planeMaterial)
|
|
51
|
+
plane.rotation.x = -Math.PI / 2
|
|
52
|
+
plane.position.y = -0.25
|
|
53
|
+
plane.receiveShadow = true
|
|
54
|
+
scene.add(plane)
|
|
162
55
|
|
|
163
|
-
// Grid lines
|
|
56
|
+
// Grid lines - blue X axis, red Z axis (matching original matrix room)
|
|
164
57
|
const lineThickness = 0.05
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
lineZ.position.set(i, lineThickness / 2, 0)
|
|
178
|
-
scene.add(lineZ)
|
|
58
|
+
const halfSize = planeSize / 2
|
|
59
|
+
|
|
60
|
+
const gridMat = new THREE.MeshBasicMaterial({ color: 0x888888 })
|
|
61
|
+
const xMat = new THREE.MeshBasicMaterial({ color: 0x0000ff }) // blue X axis
|
|
62
|
+
const zMat = new THREE.MeshBasicMaterial({ color: 0xff0000 }) // red Z axis
|
|
63
|
+
|
|
64
|
+
for (let x = -halfSize; x <= halfSize; x += gridSize) {
|
|
65
|
+
const mat = Math.abs(x) < 0.01 ? xMat : gridMat
|
|
66
|
+
const geo = new THREE.BoxGeometry(lineThickness, lineThickness, planeSize)
|
|
67
|
+
const line = new THREE.Mesh(geo, mat)
|
|
68
|
+
line.position.set(x, lineThickness / 2, 0)
|
|
69
|
+
scene.add(line)
|
|
179
70
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// Horizontal lines
|
|
187
|
-
const hLine = new THREE.Mesh(wallLineGeoH, i === 0 ? axisMaterial : gridMaterial)
|
|
188
|
-
hLine.position.set(0, -wallHeight / 2 + i + wallHeight / 2, -roomSize / 2 + wallThickness / 2 + lineThickness)
|
|
189
|
-
scene.add(hLine)
|
|
190
|
-
|
|
191
|
-
// Vertical lines
|
|
192
|
-
const vLine = new THREE.Mesh(wallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
|
|
193
|
-
vLine.position.set(-roomSize / 2 + i, 0, -roomSize / 2 + wallThickness / 2 + lineThickness)
|
|
194
|
-
scene.add(vLine)
|
|
71
|
+
for (let z = -halfSize; z <= halfSize; z += gridSize) {
|
|
72
|
+
const mat = Math.abs(z) < 0.01 ? zMat : gridMat
|
|
73
|
+
const geo = new THREE.BoxGeometry(planeSize, lineThickness, lineThickness)
|
|
74
|
+
const line = new THREE.Mesh(geo, mat)
|
|
75
|
+
line.position.set(0, lineThickness / 2, z)
|
|
76
|
+
scene.add(line)
|
|
195
77
|
}
|
|
196
78
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
const vLine = new THREE.Mesh(sideWallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
|
|
218
|
-
vLine.position.set(-roomSize / 2 + wallThickness / 2 + lineThickness, 0, -roomSize / 2 + i)
|
|
219
|
-
scene.add(vLine)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Right wall grid
|
|
223
|
-
for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
|
|
224
|
-
const hLine = new THREE.Mesh(sideWallLineGeoH, i === 0 ? axisMaterial : gridMaterial)
|
|
225
|
-
hLine.position.set(roomSize / 2 - wallThickness / 2 - lineThickness, -wallHeight / 2 + i + wallHeight / 2, 0)
|
|
226
|
-
scene.add(hLine)
|
|
227
|
-
|
|
228
|
-
const vLine = new THREE.Mesh(sideWallLineGeoV, i === 0 ? axisMaterial : gridMaterial)
|
|
229
|
-
vLine.position.set(roomSize / 2 - wallThickness / 2 - lineThickness, 0, -roomSize / 2 + i)
|
|
230
|
-
scene.add(vLine)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Ceiling grid
|
|
234
|
-
for (let i = -roomSize / 2; i <= roomSize / 2; i += gridSize) {
|
|
235
|
-
const lineX = new THREE.Mesh(lineGeo, i === 0 ? axisMaterial : gridMaterial)
|
|
236
|
-
lineX.position.set(0, wallHeight + wallThickness - lineThickness / 2, i)
|
|
237
|
-
scene.add(lineX)
|
|
238
|
-
|
|
239
|
-
const lineZ = new THREE.Mesh(crossLineGeo, i === 0 ? axisMaterial : gridMaterial)
|
|
240
|
-
lineZ.position.set(i, wallHeight + wallThickness - lineThickness / 2, 0)
|
|
241
|
-
scene.add(lineZ)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Add subtle point lights for that construct glow
|
|
245
|
-
const centerLight = new THREE.PointLight(0xffffff, 2, 50)
|
|
246
|
-
centerLight.position.set(0, wallHeight / 2, 0)
|
|
247
|
-
scene.add(centerLight)
|
|
248
|
-
|
|
249
|
-
// Corner accent lights
|
|
250
|
-
const cornerPositions = [
|
|
251
|
-
[-roomSize / 2 + 2, wallHeight - 2, -roomSize / 2 + 2],
|
|
252
|
-
[roomSize / 2 - 2, wallHeight - 2, -roomSize / 2 + 2],
|
|
253
|
-
[-roomSize / 2 + 2, wallHeight - 2, roomSize / 2 - 2],
|
|
254
|
-
[roomSize / 2 - 2, wallHeight - 2, roomSize / 2 - 2]
|
|
255
|
-
]
|
|
256
|
-
|
|
257
|
-
cornerPositions.forEach(pos => {
|
|
258
|
-
const light = new THREE.PointLight(0xffffff, 0.5, 15)
|
|
259
|
-
light.position.set(...pos)
|
|
260
|
-
scene.add(light)
|
|
79
|
+
// Origin marker
|
|
80
|
+
const originGeo = new THREE.RingGeometry(0.3, 0.5, 32)
|
|
81
|
+
const originMat = new THREE.MeshBasicMaterial({
|
|
82
|
+
color: 0x333333,
|
|
83
|
+
side: THREE.DoubleSide,
|
|
84
|
+
transparent: true,
|
|
85
|
+
opacity: 0.4
|
|
86
|
+
})
|
|
87
|
+
const origin = new THREE.Mesh(originGeo, originMat)
|
|
88
|
+
origin.rotation.x = -Math.PI / 2
|
|
89
|
+
origin.position.y = lineThickness
|
|
90
|
+
scene.add(origin)
|
|
91
|
+
|
|
92
|
+
// Outer boundary ring
|
|
93
|
+
const boundaryGeo = new THREE.RingGeometry(halfSize - 0.2, halfSize, 64)
|
|
94
|
+
const boundaryMat = new THREE.MeshBasicMaterial({
|
|
95
|
+
color: 0x666666,
|
|
96
|
+
side: THREE.DoubleSide,
|
|
97
|
+
transparent: true,
|
|
98
|
+
opacity: 0.3
|
|
261
99
|
})
|
|
100
|
+
const boundary = new THREE.Mesh(boundaryGeo, boundaryMat)
|
|
101
|
+
boundary.rotation.x = -Math.PI / 2
|
|
102
|
+
boundary.position.y = lineThickness / 2
|
|
103
|
+
scene.add(boundary)
|
|
262
104
|
|
|
263
|
-
|
|
264
|
-
this.constructRoom = { floor, ceil, backWall, frontWall, leftWall, rightWall }
|
|
105
|
+
console.log('[matrix-construct-room] done creating plane and grid')
|
|
265
106
|
}
|
|
266
107
|
}
|
|
267
108
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const CONSTRUCT = {
|
|
2
2
|
enabled: false,
|
|
3
3
|
anchorDir: [0, 1, 0],
|
|
4
4
|
radius: 1000,
|
|
@@ -44,18 +44,20 @@ export default {
|
|
|
44
44
|
feetOffset: 0.212
|
|
45
45
|
},
|
|
46
46
|
scene: {
|
|
47
|
-
skyColor:
|
|
48
|
-
fogColor:
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
skyColor: 0x87ceeb,
|
|
48
|
+
fogColor: 0x87ceeb,
|
|
49
|
+
fogType: 'exp2',
|
|
50
|
+
fogDensity: 0.001,
|
|
51
|
+
fogNear: 200,
|
|
52
|
+
fogFar: 400,
|
|
51
53
|
ambientColor: 0xffffff,
|
|
52
|
-
ambientIntensity: 0.
|
|
54
|
+
ambientIntensity: 0.6,
|
|
53
55
|
sunColor: 0xffffff,
|
|
54
|
-
sunIntensity:
|
|
55
|
-
sunPosition: [
|
|
56
|
-
fillColor:
|
|
57
|
-
fillIntensity: 0.
|
|
58
|
-
fillPosition: [
|
|
56
|
+
sunIntensity: 1.2,
|
|
57
|
+
sunPosition: [30, 60, 30],
|
|
58
|
+
fillColor: 0x4488ff,
|
|
59
|
+
fillIntensity: 0.4,
|
|
60
|
+
fillPosition: [-30, 40, -30],
|
|
59
61
|
shadowMapSize: 1024,
|
|
60
62
|
shadowBias: 0.001,
|
|
61
63
|
shadowNormalBias: 0.1
|
|
@@ -81,9 +83,9 @@ export default {
|
|
|
81
83
|
},
|
|
82
84
|
trustedApps: ['matrix-construct-room', 'tps-game'],
|
|
83
85
|
placeableApps: [],
|
|
84
|
-
terrain:
|
|
86
|
+
terrain: CONSTRUCT,
|
|
85
87
|
entities: [
|
|
86
|
-
{ id: 'terrain', app: 'terrain', config:
|
|
88
|
+
{ id: 'terrain', app: 'terrain', config: CONSTRUCT },
|
|
87
89
|
{ id: 'matrix-room', app: 'matrix-construct-room', position: [0, 0, 0] },
|
|
88
90
|
{ id: 'tps-game', position: [0, 0, 0], app: 'tps-game' }
|
|
89
91
|
],
|
|
@@ -4,8 +4,9 @@ export class MobileControls {
|
|
|
4
4
|
constructor(options = {}) {
|
|
5
5
|
this.enabled = isMobile || options.forceEnable
|
|
6
6
|
this.responsive = this._calcResponsive()
|
|
7
|
-
|
|
7
|
+
// Apply options first, then calculate layout with final values
|
|
8
8
|
this.options = { joystickRadius: this.responsive.joystickRadius, lookJoystickRadius: this.responsive.joystickRadius, buttonSize: this.responsive.buttonSize, buttonSpacing: this.responsive.spacing, deadzone: 0.12, movementDeadzone: 0.15, rotationSensitivity: 0.003, zoomSensitivity: 0.008, autoShow: true, ...options }
|
|
9
|
+
this.layout = this._calcLayout()
|
|
9
10
|
this.state = { move: { x: 0, y: 0 }, look: { x: 0, y: 0 }, lookDelta: { yaw: 0, pitch: 0 }, jump: false, shoot: false, reload: false, sprint: false, crouch: false, zoom: 0, zoomDelta: 0, interact: false, menu: false, chatWheel: false }
|
|
10
11
|
// Opt-in gyro fine-aim: device tilt adds a small yaw/pitch delta on TOP of the touch-look joystick
|
|
11
12
|
// delta, default OFF (a player who never enables it gets byte-identical touch-only behavior).
|
|
@@ -43,7 +44,7 @@ export class MobileControls {
|
|
|
43
44
|
_calcLayout() {
|
|
44
45
|
const { viewport: { w, h }, edgeMargin: m, bottomMargin: bm, joystickRadius: r, buttonSize: bs, spacing: sp } = this.responsive
|
|
45
46
|
const d = r * 2, lr = Math.min(80, w * 0.15)
|
|
46
|
-
return { moveJoystickPos: { x: m, y: -bm - d / 2 }, lookJoystickPos: { x: w - lr - d, y: -(bm + d / 2) }, moveLeft: m
|
|
47
|
+
return { moveJoystickPos: { x: m, y: -bm - d / 2 }, lookJoystickPos: { x: w - lr - d, y: -(bm + d / 2) }, moveLeft: m, moveBottom: bm + d / 2, lookRight: lr, lookBottom: bm + d / 2, buttonsBottomOffset: bm + 60, buttonsRightOffset: Math.max(10, m), buttonAreaWidth: bs * 3 + sp * 4, w, h }
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
calculateResponsiveSizes() { return this._calcResponsive() }
|
|
@@ -194,11 +195,13 @@ export class MobileControls {
|
|
|
194
195
|
|
|
195
196
|
getInput() {
|
|
196
197
|
if (!this.enabled) return null
|
|
197
|
-
const { move } = this.state, dz = 0.
|
|
198
|
+
const { move } = this.state, dz = 0.2
|
|
198
199
|
// Gyro delta adds ON TOP of the touch-look joystick delta (fine-aim nudge while thumb-dragging),
|
|
199
200
|
// not a replacement -- so touch-look stays the primary input and gyro just sharpens it.
|
|
200
201
|
const yaw = this.state.lookDelta.yaw + this._gyroDelta.yaw, pitch = this.state.lookDelta.pitch + this._gyroDelta.pitch
|
|
201
|
-
|
|
202
|
+
// Screen Y is DOWN; joystick UP (forward) = negative dy. Flip so forward = positive.
|
|
203
|
+
const forwardVal = -move.y
|
|
204
|
+
return { forward: forwardVal < -dz, backward: forwardVal > dz, left: move.x < -dz, right: move.x > dz, jump: this.state.jump, shoot: this.state.shoot, reload: this.state.reload, sprint: this.state.sprint, crouch: this.state.crouch, yaw, pitch, zoom: this.state.zoomDelta, resetZoom: () => { this.state.zoomDelta = 0 }, moveX: move.x, moveY: move.y, mouseX: 0, mouseY: 0, interact: this.state.interact, analogForward: forwardVal, analogRight: move.x, chatWheel: this.state.chatWheel }
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
hasInteraction() { return this.moveJoystick.active || this.lookJoystick.active || this.pinch.active || this.state.jump || this.state.shoot || this.state.reload || this.state.sprint || this.state.crouch || this.state.interact || this.state.chatWheel || this.state.zoomDelta !== 0 || (this.gyroAimEnabled && (this._gyroDelta.yaw !== 0 || this._gyroDelta.pitch !== 0)) }
|
|
@@ -13,7 +13,7 @@ export { installUnderwaterTint, setSeaLevelY }
|
|
|
13
13
|
export function createScene() {
|
|
14
14
|
const scene = new THREE.Scene()
|
|
15
15
|
scene.background = new THREE.Color(0x87ceeb)
|
|
16
|
-
scene.fog = new THREE.
|
|
16
|
+
scene.fog = new THREE.FogExp2(0x87ceeb, 0.0025)
|
|
17
17
|
if (typeof window !== 'undefined' && !Number.isFinite(window.__fogFar)) window.__fogFar = 200
|
|
18
18
|
installUnderwaterTint()
|
|
19
19
|
installSkeletonUploadSkip()
|
|
@@ -230,7 +230,13 @@ export function createRenderer(isMobile) {
|
|
|
230
230
|
if (_ctxLostSceneState) {
|
|
231
231
|
try {
|
|
232
232
|
if (_ctxLostSceneState.background != null) scene.background = new THREE.Color(_ctxLostSceneState.background)
|
|
233
|
-
if (_ctxLostSceneState.fog)
|
|
233
|
+
if (_ctxLostSceneState.fog) {
|
|
234
|
+
if (_ctxLostSceneState.fog.type === 'exp2') {
|
|
235
|
+
scene.fog = new THREE.FogExp2(_ctxLostSceneState.fog.color, _ctxLostSceneState.fog.density)
|
|
236
|
+
} else {
|
|
237
|
+
scene.fog = new THREE.Fog(_ctxLostSceneState.fog.color, _ctxLostSceneState.fog.near, _ctxLostSceneState.fog.far)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
234
240
|
} catch (_) {}
|
|
235
241
|
}
|
|
236
242
|
location.reload()
|
|
@@ -340,7 +346,15 @@ export function createLoaders(renderer) {
|
|
|
340
346
|
|
|
341
347
|
export function applySceneConfig(s, scene, ambient, sun, studio, camera) {
|
|
342
348
|
if (s.skyColor != null) scene.background = new THREE.Color(s.skyColor)
|
|
343
|
-
if (s.fogColor != null) {
|
|
349
|
+
if (s.fogColor != null) {
|
|
350
|
+
const _far = s.fogFar ?? 200
|
|
351
|
+
if (s.fogType === 'exp2') {
|
|
352
|
+
scene.fog = new THREE.FogExp2(s.fogColor, s.fogDensity ?? 0.0025)
|
|
353
|
+
} else {
|
|
354
|
+
scene.fog = new THREE.Fog(s.fogColor, s.fogNear ?? 80, _far)
|
|
355
|
+
}
|
|
356
|
+
if (typeof window !== 'undefined') window.__fogFar = _far
|
|
357
|
+
}
|
|
344
358
|
if (s.ambientColor != null) { ambient.color.set(s.ambientColor); ambient.intensity = s.ambientIntensity ?? 0.3 }
|
|
345
359
|
if (s.sunColor != null) { sun.color.set(s.sunColor); sun.intensity = s.sunIntensity ?? 1.5 }
|
|
346
360
|
if (s.sunPosition) sun.position.set(...s.sunPosition)
|
|
@@ -6,10 +6,10 @@ const CSS = `
|
|
|
6
6
|
@keyframes joyGlowLook{0%{box-shadow:0 0 15px color-mix(in oklab, var(--accent) 35%, transparent),inset 0 0 20px color-mix(in oklab, var(--accent) 8%, transparent)}100%{box-shadow:0 0 25px color-mix(in oklab, var(--accent) 55%, transparent),inset 0 0 30px color-mix(in oklab, var(--accent) 18%, transparent)}}
|
|
7
7
|
@keyframes fadeIn{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}
|
|
8
8
|
.mobile-joystick-container{position:absolute;pointer-events:auto;touch-action:none;opacity:0;animation:fadeIn .4s ease-out forwards;animation-delay:.1s;padding:0;background:transparent;border:0}
|
|
9
|
-
.mobile-joystick-base{position:absolute;width:100%;height:100%;border-radius:50%;background:
|
|
9
|
+
.mobile-joystick-base{position:absolute;width:100%;height:100%;border-radius:50%;background:transparent;border:1px solid color-mix(in oklab, var(--rule) 50%, transparent);box-shadow:0 2px 10px color-mix(in oklab, var(--panel-text) 8%, transparent),inset 0 1px 5px color-mix(in oklab, var(--panel-text) 4%, transparent);transition:border-color .15s,box-shadow .15s}
|
|
10
10
|
.mobile-joystick-base.active{border-color:color-mix(in oklab, var(--accent) 70%, transparent);animation:joyGlow 1.5s ease-in-out infinite}
|
|
11
11
|
.mobile-joystick-base.look-active{border-color:color-mix(in oklab, var(--accent) 60%, transparent);animation:joyGlowLook 1.5s ease-in-out infinite}
|
|
12
|
-
.mobile-joystick-knob{position:absolute;top:50%;left:50%;width:
|
|
12
|
+
.mobile-joystick-knob{position:absolute;top:50%;left:50%;width:40px;height:40px;border-radius:50%;background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--panel-text) 30%, transparent), color-mix(in oklab, var(--panel-text-3) 40%, transparent));border:1px solid color-mix(in oklab, var(--panel-text) 30%, transparent);transform:translate(-50%,-50%);box-shadow:0 2px 8px color-mix(in oklab, var(--panel-text) 8%, transparent),inset 0 1px 4px color-mix(in oklab, var(--panel-text) 15%, transparent);transition:transform .05s ease-out,background .1s}
|
|
13
13
|
.mobile-joystick-knob.active{background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--accent) 70%, transparent), color-mix(in oklab, var(--accent) 50%, transparent));border-color:color-mix(in oklab, var(--accent) 70%, transparent)}
|
|
14
14
|
.mobile-joystick-knob.look-active{background:radial-gradient(circle at 35% 35%, color-mix(in oklab, var(--accent) 65%, transparent), color-mix(in oklab, var(--accent) 50%, transparent));border-color:color-mix(in oklab, var(--accent) 70%, transparent)}
|
|
15
15
|
.mobile-joystick-directions{position:absolute;width:100%;height:100%;pointer-events:none;opacity:.3}
|
|
@@ -22,9 +22,9 @@ const CSS = `
|
|
|
22
22
|
.mobile-action-btn:active,.mobile-action-btn.active{transform:scale(.92);border-color:color-mix(in oklab, var(--accent) 80%, transparent)}
|
|
23
23
|
.mobile-action-btn .btn-icon{font-size:18px;line-height:1}
|
|
24
24
|
.mobile-action-btn .btn-label{font-size:9px;opacity:.85;margin-top:2px}
|
|
25
|
-
.mobile-zoom-controls{position:absolute;display:flex;flex-direction:
|
|
26
|
-
.mobile-zoom-btn{display:flex;align-items:center;justify-content:center;font-size:
|
|
27
|
-
.mobile-zoom-btn:active{transform:scale(.92)}
|
|
25
|
+
.mobile-zoom-controls{position:absolute;display:flex;flex-direction:column;gap:4px;pointer-events:auto;opacity:0;animation:fadeIn .4s ease-out forwards;animation-delay:.25s}
|
|
26
|
+
.mobile-zoom-btn{display:flex;align-items:center;justify-content:center;font-size:14px;font-family:var(--ff-mono, ui-monospace, monospace);padding:4px 8px;border-radius:6px;background:color-mix(in oklab, var(--panel-1) 80%, transparent);border:1px solid color-mix(in oklab, var(--rule) 50%, transparent);color:var(--panel-text);min-width:36px;min-height:36px}
|
|
27
|
+
.mobile-zoom-btn:active{transform:scale(.92);background:color-mix(in oklab, var(--panel-2) 80%, transparent)}
|
|
28
28
|
.mobile-top-bar{position:absolute;top:0;left:0;right:0;height:48px;display:flex;align-items:center;justify-content:space-between;padding:0 20px 0 max(20px, env(safe-area-inset-left));background:linear-gradient(to bottom, color-mix(in oklab, var(--panel-text) 25%, transparent), transparent);pointer-events:none;opacity:0;animation:fadeIn .4s ease-out forwards}
|
|
29
29
|
.mobile-joystick-label{position:absolute;bottom:-24px;left:50%;transform:translateX(-50%);font-size:10px;color:var(--panel-text-3);font-weight:600;text-transform:uppercase;letter-spacing:1px;white-space:nowrap;font-family:var(--ff-mono, ui-monospace, monospace)}
|
|
30
30
|
/* Safe-area insets — keep controls clear of notch & home indicator */
|
|
@@ -109,17 +109,34 @@ export function createMobileControlsUI(controls) {
|
|
|
109
109
|
container.className = 'ds-247420'
|
|
110
110
|
container.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;pointer-events:none;z-index:9999;touch-action:none;user-select:none;-webkit-user-select:none;overflow:hidden;'
|
|
111
111
|
|
|
112
|
-
|
|
112
|
+
// Move joystick - minimal visual (just base ring + knob, no directions/label)
|
|
113
|
+
const moveEl = document.createElement('div')
|
|
114
|
+
moveEl.className = 'mobile-joystick-container'
|
|
115
|
+
moveEl.id = 'move-joystick'
|
|
113
116
|
moveEl.style.left = `${lay.moveLeft}px`
|
|
114
117
|
moveEl.style.bottom = `${lay.moveBottom}px`
|
|
115
118
|
moveEl.style.width = `${d}px`
|
|
116
119
|
moveEl.style.height = `${d}px`
|
|
120
|
+
const moveBase = document.createElement('div')
|
|
121
|
+
moveBase.className = 'mobile-joystick-base'
|
|
122
|
+
moveBase.id = 'move-joystick-base'
|
|
123
|
+
const moveKnob = document.createElement('div')
|
|
124
|
+
moveKnob.className = 'mobile-joystick-knob'
|
|
125
|
+
moveKnob.id = 'move-joystick-knob'
|
|
126
|
+
moveBase.appendChild(moveKnob)
|
|
127
|
+
moveEl.appendChild(moveBase)
|
|
117
128
|
|
|
118
|
-
|
|
129
|
+
// Look joystick - minimal invisible touch area (no visual base/knob/directions)
|
|
130
|
+
const lookEl = document.createElement('div')
|
|
131
|
+
lookEl.className = 'mobile-joystick-container'
|
|
132
|
+
lookEl.id = 'look-joystick'
|
|
119
133
|
lookEl.style.right = `${lay.lookRight}px`
|
|
120
134
|
lookEl.style.bottom = `${lay.lookBottom}px`
|
|
121
135
|
lookEl.style.width = `${d}px`
|
|
122
136
|
lookEl.style.height = `${d}px`
|
|
137
|
+
lookEl.style.background = 'transparent'
|
|
138
|
+
lookEl.style.border = 'none'
|
|
139
|
+
lookEl.style.boxShadow = 'none'
|
|
123
140
|
|
|
124
141
|
// Action button cluster (kit Row-style grid).
|
|
125
142
|
const btnsEl = document.createElement('div')
|
|
@@ -140,11 +157,11 @@ export function createMobileControlsUI(controls) {
|
|
|
140
157
|
btnsEl.appendChild(useBtn)
|
|
141
158
|
btnsEl.appendChild(shootBtn)
|
|
142
159
|
|
|
143
|
-
|
|
160
|
+
// Zoom controls - top center, small buttons
|
|
144
161
|
const zoomEl = document.createElement('div')
|
|
145
|
-
zoomEl.className = 'mobile-zoom-controls
|
|
146
|
-
zoomEl.style.cssText = `
|
|
147
|
-
const zs =
|
|
162
|
+
zoomEl.className = 'mobile-zoom-controls'
|
|
163
|
+
zoomEl.style.cssText = `top:8px;left:50%;transform:translateX(-50%);pointer-events:auto;z-index:9999;display:flex;flex-direction:column;gap:4px;align-items:center;`
|
|
164
|
+
const zs = 36
|
|
148
165
|
const mkZoom = (sym, action) => {
|
|
149
166
|
const b = document.createElement('button')
|
|
150
167
|
b.className = 'btn btn-ghost mobile-zoom-btn'
|
|
@@ -212,17 +229,11 @@ export function createMobileControlsUI(controls) {
|
|
|
212
229
|
moveEl.style.top = 'auto'
|
|
213
230
|
},
|
|
214
231
|
onLookJoystickStart: (x, y) => {
|
|
215
|
-
document.getElementById('look-joystick-base')?.classList.add('look-active')
|
|
216
|
-
lookKnob.classList.add('look-active')
|
|
217
232
|
const rect = lookEl.getBoundingClientRect()
|
|
218
233
|
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }
|
|
219
234
|
},
|
|
220
|
-
onLookJoystickMove: (lx, ly) => {
|
|
221
|
-
onLookJoystickEnd: () => {
|
|
222
|
-
lookKnob.style.transform = 'translate(-50%,-50%)'
|
|
223
|
-
lookKnob.classList.remove('look-active')
|
|
224
|
-
document.getElementById('look-joystick-base')?.classList.remove('look-active')
|
|
225
|
-
},
|
|
235
|
+
onLookJoystickMove: (lx, ly) => { },
|
|
236
|
+
onLookJoystickEnd: () => { },
|
|
226
237
|
onInteractablesChanged: targets => {
|
|
227
238
|
const has = targets.size > 0
|
|
228
239
|
useBtn.dataset.action = has ? 'interact' : 'reload'
|
|
@@ -239,10 +250,9 @@ export function createMobileControlsUI(controls) {
|
|
|
239
250
|
lookEl.style.width = `${rdd}px`; lookEl.style.height = `${rdd}px`; lookEl.style.top = 'auto'
|
|
240
251
|
btnsEl.style.bottom = `${l.buttonsBottomOffset}px`
|
|
241
252
|
btnsEl.style.right = `${l.buttonsRightOffset}px`
|
|
242
|
-
|
|
243
|
-
zoomEl.style.
|
|
244
|
-
zoomEl.style.
|
|
245
|
-
zoomEl.style.transform = 'translateX(50%)'
|
|
253
|
+
zoomEl.style.top = '8px'
|
|
254
|
+
zoomEl.style.left = '50%'
|
|
255
|
+
zoomEl.style.transform = 'translateX(-50%)'
|
|
246
256
|
},
|
|
247
257
|
onDestroy: () => {
|
|
248
258
|
container.remove()
|