castle-web-cli 0.4.129 → 0.4.130
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.
- package/dist/agent-prompts.js +4 -4
- package/dist/filesChanged.d.ts +2 -0
- package/dist/filesChanged.js +6 -2
- package/dist/ide.js +15 -4
- package/dist/importBrowse.js +1 -0
- package/dist/index.js +10 -4
- package/dist/init.js +63 -15
- package/dist/native/loop.js +1 -1
- package/dist/native/tools.js +1 -1
- package/dist/serve.d.ts +1 -0
- package/dist/serve.js +20 -1
- package/dist/shell/assets/index-BpOLUyVO.js +441 -0
- package/dist/shell/assets/{index-CAq6f9B5.css → index-D6K-0YDB.css} +1 -1
- package/dist/shell/index.html +2 -2
- package/kits/physics-2d/CLAUDE.md +9 -10
- package/kits/physics-2d/castle.json +1 -1
- package/kits/physics-2d/editors/PlayOnly.jsx +15 -6
- package/kits/physics-2d/editors/PxArtEditor.jsx +95 -31
- package/kits/physics-2d/editors/SceneEditor.jsx +5 -5
- package/kits/physics-2d/editors/SingleEditor.jsx +37 -8
- package/kits/physics-2d/editors/StyleEditor.jsx +7 -4
- package/kits/physics-2d/editors/pixelEditorChrome.jsx +7 -7
- package/kits/physics-2d/editors/pixelInspector.jsx +13 -7
- package/kits/physics-2d/editors/pxArtTimeline.jsx +29 -11
- package/kits/physics-2d/editors/pxArtTimeline.module.css +11 -0
- package/kits/physics-2d/engine/blueprint.js +15 -3
- package/kits/physics-2d/engine/liveReload.js +33 -21
- package/kits/physics-2d/engine/systemRegistry.js +1 -1
- package/kits/physics-2d/engine/ui.jsx +2 -2
- package/kits/physics-2d/engine/ui.module.css +42 -0
- package/kits/physics-3d/CLAUDE.md +109 -0
- package/kits/physics-3d/behaviors/Body.jsx +51 -0
- package/kits/physics-3d/behaviors/Door.jsx +31 -0
- package/kits/physics-3d/behaviors/Lookable.jsx +11 -0
- package/kits/physics-3d/behaviors/Model.jsx +14 -0
- package/kits/physics-3d/behaviors/Pickup.jsx +39 -0
- package/kits/physics-3d/behaviors/Player.jsx +172 -0
- package/kits/physics-3d/behaviors/Portal.jsx +19 -0
- package/kits/physics-3d/behaviors/Shape.jsx +75 -0
- package/kits/physics-3d/behaviors/Solid.jsx +11 -0
- package/kits/physics-3d/behaviors/Transform.jsx +24 -0
- package/kits/physics-3d/blueprints/barrel.scene +15 -0
- package/kits/physics-3d/blueprints/crate.scene +29 -0
- package/kits/physics-3d/blueprints/door.scene +35 -0
- package/kits/physics-3d/blueprints/gem.scene +26 -0
- package/kits/physics-3d/blueprints/pillar.scene +25 -0
- package/kits/physics-3d/blueprints/platform.scene +23 -0
- package/kits/physics-3d/blueprints/player.scene +29 -0
- package/kits/physics-3d/blueprints/portal.scene +21 -0
- package/kits/physics-3d/blueprints/rock.scene +14 -0
- package/kits/physics-3d/blueprints/statue.scene +26 -0
- package/kits/physics-3d/blueprints/tree.scene +14 -0
- package/kits/physics-3d/blueprints/wall.scene +26 -0
- package/kits/{basic-2d → physics-3d}/castle.json +23 -5
- package/kits/physics-3d/docs/pxmodel-format.md +111 -0
- package/kits/physics-3d/drawings/crate.pxart +26 -0
- package/kits/physics-3d/drawings/door-edge.pxart +26 -0
- package/kits/physics-3d/drawings/door.pxart +27 -0
- package/kits/physics-3d/drawings/face.pxart +26 -0
- package/kits/physics-3d/drawings/floor.pxart +27 -0
- package/kits/physics-3d/drawings/platform.pxart +27 -0
- package/kits/physics-3d/drawings/statue.pxart +27 -0
- package/kits/physics-3d/drawings/wall.pxart +27 -0
- package/kits/physics-3d/engine3d/PlayOnly3D.jsx +32 -0
- package/kits/physics-3d/engine3d/PxModelEditor.jsx +619 -0
- package/kits/physics-3d/engine3d/Scene3DEditor.jsx +618 -0
- package/kits/physics-3d/engine3d/Scene3DPlayer.jsx +309 -0
- package/kits/physics-3d/engine3d/editor3dData.js +114 -0
- package/kits/physics-3d/engine3d/editor3dInspector.jsx +404 -0
- package/kits/physics-3d/engine3d/editorChrome.jsx +218 -0
- package/kits/physics-3d/engine3d/editorWorld.js +249 -0
- package/kits/physics-3d/engine3d/materials.js +174 -0
- package/kits/physics-3d/engine3d/meshops.js +123 -0
- package/kits/physics-3d/engine3d/modelEditorWorld.js +439 -0
- package/kits/physics-3d/engine3d/modelFiles.js +139 -0
- package/kits/physics-3d/engine3d/pxmodel.js +265 -0
- package/kits/physics-3d/engine3d/thumbnails.js +137 -0
- package/kits/physics-3d/engine3d/world3d.js +216 -0
- package/kits/{basic-2d → physics-3d}/eslint.config.js +10 -25
- package/kits/physics-3d/index.html +20 -0
- package/kits/physics-3d/main.jsx +30 -0
- package/kits/physics-3d/models/barrel.pxmodel +10 -0
- package/kits/physics-3d/models/player.pxmodel +222 -0
- package/kits/physics-3d/models/rock.pxmodel +30 -0
- package/kits/physics-3d/models/tree.pxmodel +10 -0
- package/kits/{basic-2d → physics-3d}/package-lock.json +140 -119
- package/kits/{basic-2d → physics-3d}/package.json +11 -9
- package/kits/{basic-2d → physics-3d}/pnpm-lock.yaml +162 -137
- package/kits/physics-3d/scenes/main.scene +414 -0
- package/kits/physics-3d/scenes/model-lab.scene +127 -0
- package/kits/physics-3d/systems/physics3d.js +356 -0
- package/package.json +5 -2
- package/dist/shell/assets/index-DKu9ejyh.js +0 -436
- package/kits/basic-2d/CLAUDE.md +0 -221
- package/kits/basic-2d/behaviors/Camera.jsx +0 -43
- package/kits/basic-2d/behaviors/Collider.jsx +0 -213
- package/kits/basic-2d/behaviors/Layout.jsx +0 -53
- package/kits/basic-2d/behaviors/Sprite.jsx +0 -357
- package/kits/basic-2d/behaviors/tint.js +0 -47
- package/kits/basic-2d/blueprints/cauldron.scene +0 -20
- package/kits/basic-2d/docs/pxart-format.md +0 -377
- package/kits/basic-2d/drawings/cauldron.pxart +0 -113
- package/kits/basic-2d/editors/BlueprintLibrary.jsx +0 -270
- package/kits/basic-2d/editors/ErrorBoundary.jsx +0 -59
- package/kits/basic-2d/editors/PlayOnly.jsx +0 -31
- package/kits/basic-2d/editors/PxArtEditor.jsx +0 -1092
- package/kits/basic-2d/editors/SceneEditor.jsx +0 -1780
- package/kits/basic-2d/editors/SelectionOverlay.jsx +0 -909
- package/kits/basic-2d/editors/SingleEditor.jsx +0 -122
- package/kits/basic-2d/editors/behaviorRegistry.js +0 -34
- package/kits/basic-2d/editors/editorHistory.js +0 -157
- package/kits/basic-2d/editors/inspectorSheet.js +0 -13
- package/kits/basic-2d/editors/pixelCanvas.js +0 -11
- package/kits/basic-2d/editors/pixelEditorChrome.jsx +0 -74
- package/kits/basic-2d/editors/pixelGeometry.js +0 -140
- package/kits/basic-2d/editors/pixelInspector.jsx +0 -633
- package/kits/basic-2d/editors/pxArtEditorModel.js +0 -732
- package/kits/basic-2d/editors/pxArtPlayback.js +0 -92
- package/kits/basic-2d/editors/pxArtTimeline.jsx +0 -752
- package/kits/basic-2d/editors/pxArtTimeline.module.css +0 -506
- package/kits/basic-2d/editors/pxArtTools.js +0 -232
- package/kits/basic-2d/editors/useArtboardFit.js +0 -105
- package/kits/basic-2d/engine/ScenePlayer.jsx +0 -209
- package/kits/basic-2d/engine/SceneUI.jsx +0 -59
- package/kits/basic-2d/engine/assets.js +0 -15
- package/kits/basic-2d/engine/autoInspector.jsx +0 -70
- package/kits/basic-2d/engine/behaviorExtensions.js +0 -32
- package/kits/basic-2d/engine/blueprint.js +0 -557
- package/kits/basic-2d/engine/collider.js +0 -200
- package/kits/basic-2d/engine/files.js +0 -141
- package/kits/basic-2d/engine/liveReload.js +0 -88
- package/kits/basic-2d/engine/pxart.js +0 -1032
- package/kits/basic-2d/engine/pxartSmooth.js +0 -222
- package/kits/basic-2d/engine/scene.js +0 -696
- package/kits/basic-2d/engine/spriteGeometry.js +0 -32
- package/kits/basic-2d/engine/systemRegistry.js +0 -16
- package/kits/basic-2d/engine/ui.jsx +0 -695
- package/kits/basic-2d/engine/ui.module.css +0 -2287
- package/kits/basic-2d/index.html +0 -24
- package/kits/basic-2d/main.jsx +0 -24
- package/kits/basic-2d/scenes/main.scene +0 -16
- package/kits/basic-2d/scripts/draw.mjs +0 -121
- /package/kits/{basic-2d → physics-3d}/.prettierrc +0 -0
- /package/kits/{basic-2d → physics-3d}/vite.config.js +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// The `.pxmodel` part-based model format: parse / serialize, the three.js
|
|
2
|
+
// builder, and the walk animator. See docs/pxmodel-format.md. A model is a
|
|
3
|
+
// list of named primitive parts with hierarchy -- the 3d analog of `.pxart`,
|
|
4
|
+
// built to be written by hand or by an agent.
|
|
5
|
+
import * as THREE from 'three';
|
|
6
|
+
import { makeShapeMesh, makeMaterial, artRefSignature } from './materials';
|
|
7
|
+
import { buildMeshGeometry } from './meshops';
|
|
8
|
+
|
|
9
|
+
const DEG = Math.PI / 180;
|
|
10
|
+
|
|
11
|
+
export const PART_DEFAULTS = {
|
|
12
|
+
parent: '',
|
|
13
|
+
kind: 'box',
|
|
14
|
+
position: [0, 0, 0],
|
|
15
|
+
rotation: [0, 0, 0],
|
|
16
|
+
offset: [0, 0, 0],
|
|
17
|
+
size: [1, 1, 1],
|
|
18
|
+
radius: 0.5,
|
|
19
|
+
height: 1,
|
|
20
|
+
color: '#c7cfdd',
|
|
21
|
+
art: '',
|
|
22
|
+
artRepeat: null,
|
|
23
|
+
tint: '',
|
|
24
|
+
roughness: 0.7,
|
|
25
|
+
metalness: 0,
|
|
26
|
+
clearcoat: 0,
|
|
27
|
+
emissive: '#000000',
|
|
28
|
+
emissiveIntensity: 1,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const VEC3_KEYS = new Set(['position', 'rotation', 'offset', 'size']);
|
|
32
|
+
|
|
33
|
+
function normalizeVec3(value, fallback) {
|
|
34
|
+
if (!Array.isArray(value)) return [...fallback];
|
|
35
|
+
return [0, 1, 2].map((i) => (Number.isFinite(value[i]) ? value[i] : fallback[i]));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizePart(raw, index) {
|
|
39
|
+
const part = { ...PART_DEFAULTS, ...(raw ?? {}) };
|
|
40
|
+
part.id = typeof part.id === 'string' && part.id ? part.id : `part-${index + 1}`;
|
|
41
|
+
for (const key of VEC3_KEYS) part[key] = normalizeVec3(part[key], PART_DEFAULTS[key]);
|
|
42
|
+
if (part.kind === 'mesh') {
|
|
43
|
+
part.vertices = Array.isArray(part.vertices)
|
|
44
|
+
? part.vertices.map((v) => normalizeVec3(v, [0, 0, 0]))
|
|
45
|
+
: [];
|
|
46
|
+
part.faces = Array.isArray(part.faces)
|
|
47
|
+
? part.faces
|
|
48
|
+
.map((face) =>
|
|
49
|
+
Array.isArray(face) ? face.filter((i) => Number.isInteger(i) && i >= 0) : []
|
|
50
|
+
)
|
|
51
|
+
.filter((face) => face.length >= 3)
|
|
52
|
+
: [];
|
|
53
|
+
}
|
|
54
|
+
return part;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Parse model text into { name, parts } with defaults applied, or null when
|
|
58
|
+
// it isn't a model. Duplicate ids get suffixed; a parent ref that doesn't
|
|
59
|
+
// resolve (or cycles) is dropped to root rather than rejected.
|
|
60
|
+
export function parseModel(text) {
|
|
61
|
+
let json;
|
|
62
|
+
try {
|
|
63
|
+
json = JSON.parse(text);
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (!json || typeof json !== 'object' || !Array.isArray(json.parts)) return null;
|
|
68
|
+
const parts = json.parts.map(normalizePart);
|
|
69
|
+
const seen = new Set();
|
|
70
|
+
for (const part of parts) {
|
|
71
|
+
while (seen.has(part.id)) part.id = `${part.id}-2`;
|
|
72
|
+
seen.add(part.id);
|
|
73
|
+
}
|
|
74
|
+
for (const part of parts) {
|
|
75
|
+
if (part.parent && !seen.has(part.parent)) part.parent = '';
|
|
76
|
+
}
|
|
77
|
+
for (const part of parts) {
|
|
78
|
+
const visited = new Set([part.id]);
|
|
79
|
+
let current = part;
|
|
80
|
+
while (current.parent) {
|
|
81
|
+
if (visited.has(current.parent)) {
|
|
82
|
+
part.parent = '';
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
visited.add(current.parent);
|
|
86
|
+
current = parts.find((candidate) => candidate.id === current.parent) ?? { parent: '' };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { name: typeof json.name === 'string' ? json.name : 'Model', parts };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const sameVec = (a, b) => a[0] === b[0] && a[1] === b[1] && a[2] === b[2];
|
|
93
|
+
|
|
94
|
+
// Serialize sparsely: only fields that differ from the defaults, so files stay
|
|
95
|
+
// as small as what an author would write by hand.
|
|
96
|
+
export function serializeModel(model) {
|
|
97
|
+
const parts = model.parts.map((part) => {
|
|
98
|
+
const out = { id: part.id };
|
|
99
|
+
if (part.parent) out.parent = part.parent;
|
|
100
|
+
if (part.kind !== 'box') out.kind = part.kind;
|
|
101
|
+
for (const key of ['position', 'rotation', 'offset']) {
|
|
102
|
+
if (!sameVec(part[key], PART_DEFAULTS[key])) out[key] = part[key];
|
|
103
|
+
}
|
|
104
|
+
if (part.kind === 'mesh') {
|
|
105
|
+
out.vertices = part.vertices;
|
|
106
|
+
out.faces = part.faces;
|
|
107
|
+
} else if (part.kind === 'box') {
|
|
108
|
+
if (!sameVec(part.size, PART_DEFAULTS.size)) out.size = part.size;
|
|
109
|
+
} else {
|
|
110
|
+
if (part.radius !== PART_DEFAULTS.radius) out.radius = part.radius;
|
|
111
|
+
if (part.kind !== 'sphere' && part.height !== PART_DEFAULTS.height) out.height = part.height;
|
|
112
|
+
}
|
|
113
|
+
for (const key of [
|
|
114
|
+
'color',
|
|
115
|
+
'art',
|
|
116
|
+
'artRepeat',
|
|
117
|
+
'tint',
|
|
118
|
+
'roughness',
|
|
119
|
+
'metalness',
|
|
120
|
+
'clearcoat',
|
|
121
|
+
'emissive',
|
|
122
|
+
'emissiveIntensity',
|
|
123
|
+
]) {
|
|
124
|
+
const value = part[key];
|
|
125
|
+
const fallback = PART_DEFAULTS[key];
|
|
126
|
+
const differs = key === 'artRepeat' ? value != null : value !== fallback;
|
|
127
|
+
if (differs) out[key] = value;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
});
|
|
131
|
+
return `${JSON.stringify({ format: 'pxmodel', name: model.name, parts }, null, 2)}\n`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const STARTER_MODEL_TEXT = serializeModel({
|
|
135
|
+
name: 'New Model',
|
|
136
|
+
parts: [normalizePart({ id: 'body', size: [1, 1, 1], position: [0, 0.5, 0] }, 0)],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Bounded parse cache -- model text is re-seen constantly by signatures and
|
|
140
|
+
// rebuilds; parsing is cheap but not free.
|
|
141
|
+
const parseCache = new Map();
|
|
142
|
+
export function parseModelCached(text) {
|
|
143
|
+
if (typeof text !== 'string') return null;
|
|
144
|
+
const cached = parseCache.get(text);
|
|
145
|
+
if (cached !== undefined) return cached;
|
|
146
|
+
if (parseCache.size > 200) parseCache.clear();
|
|
147
|
+
const model = parseModel(text);
|
|
148
|
+
parseCache.set(text, model);
|
|
149
|
+
return model;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// A part expressed in the Shape behavior's vocabulary, so the mesh builder is
|
|
153
|
+
// shared with everything else.
|
|
154
|
+
function partAsShape(part) {
|
|
155
|
+
return {
|
|
156
|
+
kind: part.kind,
|
|
157
|
+
width: part.size[0],
|
|
158
|
+
height: part.kind === 'box' ? part.size[1] : part.height,
|
|
159
|
+
depth: part.size[2],
|
|
160
|
+
radius: part.kind === 'sphere' ? part.radius : part.radius,
|
|
161
|
+
color: part.color,
|
|
162
|
+
art: part.art,
|
|
163
|
+
artRepeat: part.artRepeat,
|
|
164
|
+
tint: part.tint,
|
|
165
|
+
roughness: part.roughness,
|
|
166
|
+
metalness: part.metalness,
|
|
167
|
+
clearcoat: part.clearcoat,
|
|
168
|
+
emissive: part.emissive,
|
|
169
|
+
emissiveIntensity: part.emissiveIntensity,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Build the model as a THREE.Group: one group per part at its pivot (child
|
|
174
|
+
// groups nested under their parent's group), the mesh offset inside. The
|
|
175
|
+
// returned group's userData.modelParts maps part id -> group.
|
|
176
|
+
export function buildModelNode(model, sprites) {
|
|
177
|
+
const root = new THREE.Group();
|
|
178
|
+
const groups = new Map();
|
|
179
|
+
for (const part of model.parts) {
|
|
180
|
+
const group = new THREE.Group();
|
|
181
|
+
group.userData.partId = part.id;
|
|
182
|
+
group.position.set(part.position[0], part.position[1], part.position[2]);
|
|
183
|
+
group.rotation.set(part.rotation[0] * DEG, part.rotation[1] * DEG, part.rotation[2] * DEG);
|
|
184
|
+
let mesh;
|
|
185
|
+
if (part.kind === 'mesh') {
|
|
186
|
+
const { geometry, triFaces } = buildMeshGeometry(part);
|
|
187
|
+
mesh = new THREE.Mesh(geometry, makeMaterial(partAsShape(part), null));
|
|
188
|
+
mesh.castShadow = true;
|
|
189
|
+
mesh.receiveShadow = true;
|
|
190
|
+
mesh.userData.triFaces = triFaces;
|
|
191
|
+
} else {
|
|
192
|
+
mesh = makeShapeMesh(partAsShape(part), sprites);
|
|
193
|
+
}
|
|
194
|
+
mesh.userData.partMesh = part.id;
|
|
195
|
+
mesh.position.set(part.offset[0], part.offset[1], part.offset[2]);
|
|
196
|
+
group.add(mesh);
|
|
197
|
+
groups.set(part.id, group);
|
|
198
|
+
}
|
|
199
|
+
for (const part of model.parts) {
|
|
200
|
+
const parent = part.parent ? groups.get(part.parent) : null;
|
|
201
|
+
(parent ?? root).add(groups.get(part.id));
|
|
202
|
+
}
|
|
203
|
+
root.userData.modelParts = groups;
|
|
204
|
+
return root;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Every art ref a model uses, for change signatures.
|
|
208
|
+
export function modelArtSignature(model, sprites) {
|
|
209
|
+
if (!model) return '';
|
|
210
|
+
const refs = [];
|
|
211
|
+
for (const part of model.parts) {
|
|
212
|
+
if (typeof part.art === 'string' && part.art) refs.push(part.art);
|
|
213
|
+
else if (part.art && typeof part.art === 'object') {
|
|
214
|
+
refs.push(...Object.values(part.art).filter((ref) => typeof ref === 'string'));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return refs.map((ref) => artRefSignature(ref, sprites)).join('|');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Walk animation by part-id convention (legL/legR/armL/armR/torso), driven by
|
|
221
|
+
// observed motion: speed from position deltas, air from vertical speed, lean
|
|
222
|
+
// from yaw rate. dt of 0 (editor views) holds the neutral pose.
|
|
223
|
+
export function animateModelParts(entry, dt) {
|
|
224
|
+
const partGroups = entry.node.userData.modelParts;
|
|
225
|
+
if (!partGroups) return;
|
|
226
|
+
const parts = {
|
|
227
|
+
legL: partGroups.get('legL'),
|
|
228
|
+
legR: partGroups.get('legR'),
|
|
229
|
+
armL: partGroups.get('armL'),
|
|
230
|
+
armR: partGroups.get('armR'),
|
|
231
|
+
torso: partGroups.get('torso'),
|
|
232
|
+
};
|
|
233
|
+
if (!parts.legL && !parts.torso) return;
|
|
234
|
+
const anim = (entry.modelAnim ??= { phase: 0, swing: 0, air: 0, lean: 0, last: null });
|
|
235
|
+
const p = entry.node.position;
|
|
236
|
+
if (dt > 0 && anim.last) {
|
|
237
|
+
const speed = Math.hypot(p.x - anim.last.x, p.z - anim.last.z) / dt;
|
|
238
|
+
const verticalSpeed = (p.y - anim.last.y) / dt;
|
|
239
|
+
anim.swing += (Math.min(1, speed / 5) - anim.swing) * Math.min(1, dt * 10);
|
|
240
|
+
anim.air += ((Math.abs(verticalSpeed) > 1.2 ? 1 : 0) - anim.air) * Math.min(1, dt * 8);
|
|
241
|
+
anim.phase += Math.min(speed, 8) * 2.4 * dt;
|
|
242
|
+
const yaw = entry.node.rotation.y;
|
|
243
|
+
let yawDelta = yaw - (anim.lastYaw ?? yaw);
|
|
244
|
+
yawDelta = Math.atan2(Math.sin(yawDelta), Math.cos(yawDelta));
|
|
245
|
+
const leanTarget = Math.max(-0.13, Math.min(0.13, (yawDelta / dt) * 0.04));
|
|
246
|
+
anim.lean += (leanTarget - anim.lean) * Math.min(1, dt * 9);
|
|
247
|
+
anim.lastYaw = yaw;
|
|
248
|
+
}
|
|
249
|
+
anim.last = { x: p.x, y: p.y, z: p.z };
|
|
250
|
+
|
|
251
|
+
const swing = Math.sin(anim.phase) * 0.8 * anim.swing * (1 - anim.air);
|
|
252
|
+
if (parts.legL) parts.legL.rotation.x = swing + anim.air * -0.55;
|
|
253
|
+
if (parts.legR) parts.legR.rotation.x = -swing + anim.air * -0.3;
|
|
254
|
+
if (parts.armL) parts.armL.rotation.x = -swing * 0.75 + anim.air * -1.5;
|
|
255
|
+
if (parts.armR) parts.armR.rotation.x = swing * 0.75 + anim.air * -1.5;
|
|
256
|
+
if (parts.torso) {
|
|
257
|
+
const base =
|
|
258
|
+
parts.torso.userData.basePosition ??
|
|
259
|
+
(parts.torso.userData.basePosition = parts.torso.position.y);
|
|
260
|
+
parts.torso.position.y =
|
|
261
|
+
base + Math.abs(Math.sin(anim.phase)) * 0.05 * anim.swing * (1 - anim.air);
|
|
262
|
+
parts.torso.rotation.z = -anim.lean;
|
|
263
|
+
parts.torso.rotation.y = anim.lean * 0.4;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// 3d blueprint thumbnails for the imported hotbar / inspector chrome. The
|
|
2
|
+
// imported BlueprintThumbnail only knows how to draw `Sprite.file` art from
|
|
3
|
+
// the sprites map, so we render each blueprint template from a corner view
|
|
4
|
+
// into an offscreen canvas, wrap that canvas as an image-shaped art object,
|
|
5
|
+
// and register it in the sprites map under a virtual `@thumb/<path>` ref that
|
|
6
|
+
// gets injected as the template's `Sprite.file`. Renders are cached by the
|
|
7
|
+
// template's node signature; one shared WebGL renderer serves all thumbs.
|
|
8
|
+
import * as THREE from 'three';
|
|
9
|
+
import { makeShapeMesh, nodeSignature, disposeNode } from './materials';
|
|
10
|
+
import { parseModelCached, buildModelNode, modelArtSignature } from './pxmodel';
|
|
11
|
+
import { findBehaviorClass } from '@imports/castle.physics-2d/editors/behaviorRegistry';
|
|
12
|
+
|
|
13
|
+
export const THUMB_PREFIX = '@thumb/';
|
|
14
|
+
const THUMB_SIZE = 192;
|
|
15
|
+
|
|
16
|
+
let sharedRenderer = null;
|
|
17
|
+
let releaseTimer = 0;
|
|
18
|
+
|
|
19
|
+
function ensureRenderer() {
|
|
20
|
+
if (sharedRenderer) return sharedRenderer;
|
|
21
|
+
const canvas = document.createElement('canvas');
|
|
22
|
+
canvas.width = THUMB_SIZE;
|
|
23
|
+
canvas.height = THUMB_SIZE;
|
|
24
|
+
sharedRenderer = new THREE.WebGLRenderer({
|
|
25
|
+
canvas,
|
|
26
|
+
antialias: true,
|
|
27
|
+
alpha: true,
|
|
28
|
+
preserveDrawingBuffer: true,
|
|
29
|
+
});
|
|
30
|
+
sharedRenderer.setClearColor(0x000000, 0);
|
|
31
|
+
return sharedRenderer;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Browsers cap LIVE WebGL contexts (~16); when the cap is hit they kill the
|
|
35
|
+
// oldest, which has white-screened an editor viewport. Thumbs render in
|
|
36
|
+
// bursts and then sit cached, so release this renderer's context shortly
|
|
37
|
+
// after the last render instead of holding a slot forever.
|
|
38
|
+
function releaseRendererSoon() {
|
|
39
|
+
window.clearTimeout(releaseTimer);
|
|
40
|
+
releaseTimer = window.setTimeout(() => {
|
|
41
|
+
if (!sharedRenderer) return;
|
|
42
|
+
sharedRenderer.forceContextLoss();
|
|
43
|
+
sharedRenderer.dispose();
|
|
44
|
+
sharedRenderer = null;
|
|
45
|
+
}, 2000);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function renderTemplate(template, sprites, files) {
|
|
49
|
+
const renderer = ensureRenderer();
|
|
50
|
+
const scene = new THREE.Scene();
|
|
51
|
+
// Match the main rig's exposure -- brighter than this blows the albedo out
|
|
52
|
+
// to white.
|
|
53
|
+
scene.add(new THREE.HemisphereLight(0xc8d0d8, 0x3a3530, 0.55 * Math.PI));
|
|
54
|
+
scene.add(new THREE.AmbientLight(0xffffff, 0.25));
|
|
55
|
+
const sun = new THREE.DirectionalLight(0xffffff, 0.9);
|
|
56
|
+
sun.position.set(3, 5, 2);
|
|
57
|
+
scene.add(sun);
|
|
58
|
+
|
|
59
|
+
const node = new THREE.Group();
|
|
60
|
+
const components = template.components ?? {};
|
|
61
|
+
// Templates are raw file data -- merge each behavior's defaultProps like the
|
|
62
|
+
// scene runtime would, so unspecified fields don't go undefined.
|
|
63
|
+
const merged = (name) => {
|
|
64
|
+
const defaults = findBehaviorClass(name)?.defaultProps ?? {};
|
|
65
|
+
return { ...defaults, ...(components[name] ?? {}) };
|
|
66
|
+
};
|
|
67
|
+
const model = components.Model?.file ? parseModelCached(files?.[components.Model.file]) : null;
|
|
68
|
+
if (model) node.add(buildModelNode(model, sprites));
|
|
69
|
+
else if (components.Shape) node.add(makeShapeMesh(merged('Shape'), sprites));
|
|
70
|
+
scene.add(node);
|
|
71
|
+
|
|
72
|
+
const bounds = new THREE.Box3().setFromObject(node);
|
|
73
|
+
const center = bounds.getCenter(new THREE.Vector3());
|
|
74
|
+
const radius = Math.max(0.2, bounds.getSize(new THREE.Vector3()).length() / 2);
|
|
75
|
+
const camera = new THREE.PerspectiveCamera(30, 1, 0.01, 100);
|
|
76
|
+
camera.position
|
|
77
|
+
.set(1, 0.75, 1)
|
|
78
|
+
.normalize()
|
|
79
|
+
.multiplyScalar(radius * 3.9)
|
|
80
|
+
.add(center);
|
|
81
|
+
camera.lookAt(center);
|
|
82
|
+
|
|
83
|
+
renderer.setSize(THUMB_SIZE, THUMB_SIZE, false);
|
|
84
|
+
renderer.render(scene, camera);
|
|
85
|
+
|
|
86
|
+
const out = document.createElement('canvas');
|
|
87
|
+
out.width = THUMB_SIZE;
|
|
88
|
+
out.height = THUMB_SIZE;
|
|
89
|
+
out.getContext('2d')?.drawImage(renderer.domElement, 0, 0);
|
|
90
|
+
disposeNode(node);
|
|
91
|
+
releaseRendererSoon();
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const cache = new Map(); // blueprint path -> { sig, art }
|
|
96
|
+
|
|
97
|
+
// Image-shaped art object for one blueprint, rendered fresh only when its
|
|
98
|
+
// template (or a texture / model file it uses) changed.
|
|
99
|
+
export function thumbArtFor(path, template, sprites, files) {
|
|
100
|
+
const modelFile = template.components?.Model?.file;
|
|
101
|
+
const modelText = modelFile ? (files?.[modelFile] ?? '') : '';
|
|
102
|
+
const modelSig = modelText
|
|
103
|
+
? `${modelText.length}:${modelArtSignature(parseModelCached(modelText), sprites)}:${modelText}`
|
|
104
|
+
: '';
|
|
105
|
+
const sig = nodeSignature({ components: template.components ?? {} }, sprites, modelSig);
|
|
106
|
+
const cached = cache.get(path);
|
|
107
|
+
if (cached && cached.sig === sig) return cached.art;
|
|
108
|
+
const canvas = renderTemplate(template, sprites, files);
|
|
109
|
+
const art = {
|
|
110
|
+
kind: 'image',
|
|
111
|
+
path,
|
|
112
|
+
url: `${THUMB_PREFIX}${path}`,
|
|
113
|
+
image: canvas,
|
|
114
|
+
ready: true,
|
|
115
|
+
failed: false,
|
|
116
|
+
resolution: { width: canvas.width, height: canvas.height },
|
|
117
|
+
frames: [{ durationMs: 0 }],
|
|
118
|
+
layers: [],
|
|
119
|
+
palette: [],
|
|
120
|
+
tags: [],
|
|
121
|
+
defaultTag: '',
|
|
122
|
+
defaultDurationMs: 0,
|
|
123
|
+
cornerRadius: 0,
|
|
124
|
+
};
|
|
125
|
+
cache.set(path, { sig, art });
|
|
126
|
+
return art;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// A template clone whose Sprite points at the virtual thumb ref, for handing
|
|
130
|
+
// to the imported BlueprintThumbnail.
|
|
131
|
+
export function withThumbRef(template, path) {
|
|
132
|
+
if (!template) return template;
|
|
133
|
+
return {
|
|
134
|
+
...template,
|
|
135
|
+
components: { ...(template.components ?? {}), Sprite: { file: `${THUMB_PREFIX}${path}` } },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// The three.js side of the kit: renderer, lighting rig, ground, and per-actor
|
|
2
|
+
// nodes built from Shape components. Values follow the vase reference rig --
|
|
3
|
+
// hemisphere + ambient + one tuned shadow-casting directional, PCFSoft shadows,
|
|
4
|
+
// EffectComposer with RenderPass + OutputPass. Actor Transforms are the source
|
|
5
|
+
// of truth; this module only reads them.
|
|
6
|
+
import * as THREE from 'three';
|
|
7
|
+
import { EffectComposer, RenderPass, OutputPass } from 'three/examples/jsm/Addons.js';
|
|
8
|
+
import {
|
|
9
|
+
makeShapeMesh,
|
|
10
|
+
nodeSignature,
|
|
11
|
+
disposeNode,
|
|
12
|
+
resolveArtTexture,
|
|
13
|
+
artRefSignature,
|
|
14
|
+
} from './materials';
|
|
15
|
+
import { parseModelCached, buildModelNode, modelArtSignature, animateModelParts } from './pxmodel';
|
|
16
|
+
|
|
17
|
+
const GROUND_SIZE = 60;
|
|
18
|
+
|
|
19
|
+
export function makeWorld3D(canvas) {
|
|
20
|
+
const scene = new THREE.Scene();
|
|
21
|
+
const aspect = canvas.clientWidth / Math.max(1, canvas.clientHeight);
|
|
22
|
+
const camera = new THREE.PerspectiveCamera(50, aspect, 0.1, 1000);
|
|
23
|
+
camera.position.set(8, 8, 8);
|
|
24
|
+
camera.lookAt(0, 0, 0);
|
|
25
|
+
|
|
26
|
+
const renderer = new THREE.WebGLRenderer({
|
|
27
|
+
canvas,
|
|
28
|
+
antialias: true,
|
|
29
|
+
preserveDrawingBuffer: true, // screenshots read the canvas after the frame
|
|
30
|
+
});
|
|
31
|
+
// If the browser reclaims this context (live-context cap), ask for the
|
|
32
|
+
// automatic restore instead of staying a white canvas forever.
|
|
33
|
+
canvas.addEventListener('webglcontextlost', (event) => event.preventDefault());
|
|
34
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
35
|
+
renderer.setSize(canvas.clientWidth, canvas.clientHeight, false);
|
|
36
|
+
renderer.setClearColor(0x000000);
|
|
37
|
+
renderer.shadowMap.enabled = true;
|
|
38
|
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
39
|
+
|
|
40
|
+
addLights(scene);
|
|
41
|
+
const { ground, grid } = addGround(scene);
|
|
42
|
+
|
|
43
|
+
const composer = new EffectComposer(renderer);
|
|
44
|
+
composer.addPass(new RenderPass(scene, camera));
|
|
45
|
+
composer.addPass(new OutputPass());
|
|
46
|
+
|
|
47
|
+
// Collider wireframes from rapier's debugRender, toggled with `p`.
|
|
48
|
+
const debugLines = new THREE.LineSegments(
|
|
49
|
+
new THREE.BufferGeometry(),
|
|
50
|
+
new THREE.LineBasicMaterial({ vertexColors: true })
|
|
51
|
+
);
|
|
52
|
+
debugLines.visible = false;
|
|
53
|
+
debugLines.frustumCulled = false;
|
|
54
|
+
scene.add(debugLines);
|
|
55
|
+
|
|
56
|
+
return { scene, camera, renderer, composer, nodes: new Map(), debugLines, ground, grid };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function updatePhysicsDebug(world, buffers) {
|
|
60
|
+
if (!buffers) return;
|
|
61
|
+
const geometry = new THREE.BufferGeometry();
|
|
62
|
+
geometry.setAttribute('position', new THREE.BufferAttribute(buffers.vertices, 3));
|
|
63
|
+
geometry.setAttribute('color', new THREE.BufferAttribute(buffers.colors, 4));
|
|
64
|
+
world.debugLines.geometry.dispose();
|
|
65
|
+
world.debugLines.geometry = geometry;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function addLights(scene) {
|
|
69
|
+
// The vase rig, lifted -- brighter overall than the raw vase values, with a
|
|
70
|
+
// stronger shadow term so shadows stay readable against the lift.
|
|
71
|
+
scene.add(new THREE.HemisphereLight(0xc8d0d8, 0x3a3530, 0.62 * Math.PI));
|
|
72
|
+
scene.add(new THREE.AmbientLight(0xffffff, 0.24));
|
|
73
|
+
|
|
74
|
+
const sun = new THREE.DirectionalLight(0xffffff, 1.15);
|
|
75
|
+
sun.position.set(6, 18, -6);
|
|
76
|
+
sun.castShadow = true;
|
|
77
|
+
sun.shadow.intensity = 0.65;
|
|
78
|
+
sun.shadow.mapSize.width = 2048;
|
|
79
|
+
sun.shadow.mapSize.height = 2048;
|
|
80
|
+
sun.shadow.camera.near = 0.5;
|
|
81
|
+
sun.shadow.camera.far = 50;
|
|
82
|
+
sun.shadow.camera.top = 85;
|
|
83
|
+
sun.shadow.camera.bottom = -35;
|
|
84
|
+
sun.shadow.camera.left = -75;
|
|
85
|
+
sun.shadow.camera.right = 75;
|
|
86
|
+
sun.shadow.normalBias = -0.14;
|
|
87
|
+
scene.add(sun);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function addGround(scene) {
|
|
91
|
+
const ground = new THREE.Mesh(
|
|
92
|
+
new THREE.PlaneGeometry(GROUND_SIZE, GROUND_SIZE),
|
|
93
|
+
new THREE.MeshStandardMaterial({ color: 0x22222b, dithering: true })
|
|
94
|
+
);
|
|
95
|
+
ground.rotation.x = -Math.PI / 2;
|
|
96
|
+
ground.position.y = -0.01;
|
|
97
|
+
ground.receiveShadow = true;
|
|
98
|
+
scene.add(ground);
|
|
99
|
+
|
|
100
|
+
const grid = new THREE.GridHelper(GROUND_SIZE, GROUND_SIZE, 0x505050, 0x505050);
|
|
101
|
+
grid.material.transparent = true;
|
|
102
|
+
grid.material.opacity = 0.35;
|
|
103
|
+
scene.add(grid);
|
|
104
|
+
return { ground, grid };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A scene can dress the ground: `"ground": { "art": "drawings/floor.pxart",
|
|
108
|
+
// "tile": 2, "color": "#..." }` at the scene file's top level. `tile` is the
|
|
109
|
+
// world size of one texture repeat (meters). The line grid only shows on the
|
|
110
|
+
// untextured ground -- over art it reads as noise.
|
|
111
|
+
export function syncGround(world, setting, sprites) {
|
|
112
|
+
const sig = `${JSON.stringify(setting ?? null)}|${artRefSignature(setting?.art, sprites)}`;
|
|
113
|
+
if (sig === world.groundSig) return;
|
|
114
|
+
const tile = setting?.tile > 0 ? setting.tile : 2;
|
|
115
|
+
const repeat = [GROUND_SIZE / tile, GROUND_SIZE / tile];
|
|
116
|
+
const texture = setting?.art ? resolveArtTexture(setting.art, sprites, repeat) : null;
|
|
117
|
+
world.groundSig = sig;
|
|
118
|
+
const material = new THREE.MeshStandardMaterial({
|
|
119
|
+
color: new THREE.Color(setting?.color ?? (texture ? '#ffffff' : '#22222b')),
|
|
120
|
+
map: texture,
|
|
121
|
+
dithering: true,
|
|
122
|
+
});
|
|
123
|
+
world.ground.material.dispose();
|
|
124
|
+
world.ground.material = material;
|
|
125
|
+
world.grid.visible = !texture;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// The signature suffix for an actor's Model component: the model file's text
|
|
129
|
+
// plus the readiness/identity of any art it references.
|
|
130
|
+
function actorModelSig(actor, sprites, files) {
|
|
131
|
+
const file = actor.components.Model?.file;
|
|
132
|
+
if (!file) return '';
|
|
133
|
+
const text = files?.[file];
|
|
134
|
+
if (typeof text !== 'string') return 'missing';
|
|
135
|
+
return `${text.length}:${modelArtSignature(parseModelCached(text), sprites)}:${text}`;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Build / refresh the node for one actor. Rebuilds only when what it looks
|
|
139
|
+
// like (Shape / Model / their art) changes. A Model replaces the Shape's own
|
|
140
|
+
// mesh with the .pxmodel rig; the Shape still sizes the physics collider.
|
|
141
|
+
function ensureActorNode(world, actor, sprites, files) {
|
|
142
|
+
const sig = nodeSignature(actor, sprites, actorModelSig(actor, sprites, files));
|
|
143
|
+
let entry = world.nodes.get(actor.id);
|
|
144
|
+
if (entry && entry.sig !== sig) {
|
|
145
|
+
world.scene.remove(entry.node);
|
|
146
|
+
disposeNode(entry.node);
|
|
147
|
+
entry = null;
|
|
148
|
+
}
|
|
149
|
+
if (!entry) {
|
|
150
|
+
const node = new THREE.Group();
|
|
151
|
+
node.userData.actorId = actor.id; // raycast hits map back to the actor
|
|
152
|
+
const modelComponent = actor.components.Model;
|
|
153
|
+
const shape = actor.components.Shape;
|
|
154
|
+
const model = modelComponent?.file ? parseModelCached(files?.[modelComponent.file]) : null;
|
|
155
|
+
if (model) {
|
|
156
|
+
const rig = buildModelNode(model, sprites);
|
|
157
|
+
rig.position.y = modelComponent.yOffset ?? 0;
|
|
158
|
+
const scale = modelComponent.scale ?? 1;
|
|
159
|
+
rig.scale.set(scale, scale, scale);
|
|
160
|
+
node.userData.modelParts = rig.userData.modelParts;
|
|
161
|
+
node.add(rig);
|
|
162
|
+
} else if (shape) {
|
|
163
|
+
node.add(makeShapeMesh(shape, sprites));
|
|
164
|
+
}
|
|
165
|
+
world.scene.add(node);
|
|
166
|
+
entry = { node, sig };
|
|
167
|
+
world.nodes.set(actor.id, entry);
|
|
168
|
+
}
|
|
169
|
+
return entry;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const DEG = Math.PI / 180;
|
|
173
|
+
|
|
174
|
+
// Copy actor Transforms into three nodes, creating and removing nodes as
|
|
175
|
+
// actors come and go. Dynamic bodies leave an exact quaternion in
|
|
176
|
+
// `actor.runtime.quat`; everything else rotates by the Transform's euler
|
|
177
|
+
// degrees.
|
|
178
|
+
export function syncActors(world, runtime, sprites, dt = 0) {
|
|
179
|
+
syncGround(world, runtime.data?.ground, sprites);
|
|
180
|
+
const seen = new Set();
|
|
181
|
+
for (const actor of runtime.getActors()) {
|
|
182
|
+
const t = actor.components.Transform;
|
|
183
|
+
if (!t) continue;
|
|
184
|
+
seen.add(actor.id);
|
|
185
|
+
const entry = ensureActorNode(world, actor, sprites, runtime.files);
|
|
186
|
+
const node = entry.node;
|
|
187
|
+
node.position.set(t.x, t.y, t.z);
|
|
188
|
+
const quat = actor.runtime?.quat;
|
|
189
|
+
if (quat) node.quaternion.set(quat.x, quat.y, quat.z, quat.w);
|
|
190
|
+
else node.rotation.set(t.rotationX * DEG, t.rotationY * DEG, t.rotationZ * DEG);
|
|
191
|
+
animateModelParts(entry, dt);
|
|
192
|
+
}
|
|
193
|
+
for (const [id, entry] of world.nodes) {
|
|
194
|
+
if (seen.has(id)) continue;
|
|
195
|
+
world.scene.remove(entry.node);
|
|
196
|
+
disposeNode(entry.node);
|
|
197
|
+
world.nodes.delete(id);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function resizeWorld(world, width, height) {
|
|
202
|
+
world.renderer.setSize(width, height, false);
|
|
203
|
+
world.composer.setSize(width, height);
|
|
204
|
+
world.camera.aspect = width / Math.max(1, height);
|
|
205
|
+
world.camera.updateProjectionMatrix();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function renderWorld(world) {
|
|
209
|
+
world.composer.render();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function disposeWorld(world) {
|
|
213
|
+
for (const entry of world.nodes.values()) disposeNode(entry.node);
|
|
214
|
+
world.nodes.clear();
|
|
215
|
+
world.renderer.dispose();
|
|
216
|
+
}
|
|
@@ -3,32 +3,17 @@ import reactHooks from 'eslint-plugin-react-hooks';
|
|
|
3
3
|
|
|
4
4
|
export default [
|
|
5
5
|
{
|
|
6
|
-
ignores: ['node_modules/**', 'dist/**', '.castle/**', '*.config.js'],
|
|
6
|
+
ignores: ['node_modules/**', 'dist/**', '.castle/**', 'imports/**', '*.config.js'],
|
|
7
7
|
},
|
|
8
8
|
js.configs.recommended,
|
|
9
9
|
{
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
process: 'readonly',
|
|
18
|
-
Buffer: 'readonly',
|
|
19
|
-
console: 'readonly',
|
|
20
|
-
URL: 'readonly',
|
|
21
|
-
setTimeout: 'readonly',
|
|
22
|
-
clearTimeout: 'readonly',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
rules: {
|
|
26
|
-
'no-undef': 'off',
|
|
27
|
-
'no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
files: ['engine/**/*.{js,jsx}', 'editors/**/*.{js,jsx}', 'behaviors/**/*.{js,jsx}', '*.js', '*.jsx'],
|
|
10
|
+
files: [
|
|
11
|
+
'engine3d/**/*.{js,jsx}',
|
|
12
|
+
'systems/**/*.{js,jsx}',
|
|
13
|
+
'behaviors/**/*.{js,jsx}',
|
|
14
|
+
'*.js',
|
|
15
|
+
'*.jsx',
|
|
16
|
+
],
|
|
32
17
|
languageOptions: {
|
|
33
18
|
ecmaVersion: 'latest',
|
|
34
19
|
sourceType: 'module',
|
|
@@ -45,16 +30,16 @@ export default [
|
|
|
45
30
|
setTimeout: 'readonly',
|
|
46
31
|
clearTimeout: 'readonly',
|
|
47
32
|
URL: 'readonly',
|
|
33
|
+
URLSearchParams: 'readonly',
|
|
48
34
|
fetch: 'readonly',
|
|
49
35
|
navigator: 'readonly',
|
|
50
36
|
location: 'readonly',
|
|
51
37
|
HTMLCanvasElement: 'readonly',
|
|
52
38
|
Image: 'readonly',
|
|
53
|
-
|
|
39
|
+
ResizeObserver: 'readonly',
|
|
54
40
|
KeyboardEvent: 'readonly',
|
|
55
41
|
PointerEvent: 'readonly',
|
|
56
42
|
MouseEvent: 'readonly',
|
|
57
|
-
TouchEvent: 'readonly',
|
|
58
43
|
structuredClone: 'readonly',
|
|
59
44
|
},
|
|
60
45
|
},
|