Needle Engine

Changes between version 3.23.0 and 3.23.1
Files changed (9) hide show
  1. src/engine-components/AudioSource.ts +4 -0
  2. src/engine-components/Collider.ts +3 -1
  3. src/engine/engine_gizmos.ts +115 -5
  4. src/engine/engine_physics_rapier.ts +1 -1
  5. src/engine/engine_three_utils.ts +27 -0
  6. src/engine-components/utils/LookAt.ts +3 -19
  7. src/engine-components/webxr/WebXRPlaneTracking.ts +87 -24
  8. src/include/needle/arial-msdf.json +1472 -0
  9. src/include/needle/arial.png +0 -0
src/engine-components/AudioSource.ts CHANGED
@@ -320,6 +320,10 @@
320
320
 
321
321
  /** Play a mediastream */
322
322
  play(clip: string | MediaStream | undefined = undefined) {
323
+ // use audio source's clip when no clip is passed in
324
+ if(!clip && this.clip)
325
+ clip = this.clip;
326
+
323
327
  // We only support strings and media stream
324
328
  // TODO: maybe we should return here if an invalid value is passed in
325
329
  if (clip !== undefined && typeof clip !== "string" && !(clip instanceof MediaStream)) {
src/engine-components/Collider.ts CHANGED
@@ -109,9 +109,11 @@
109
109
 
110
110
  export class MeshCollider extends Collider {
111
111
 
112
-
112
+ @serializable()
113
113
  sharedMesh?: Mesh;
114
114
 
115
+ /** When `true` the collider won't have holes or entrances.
116
+ * If you wan't this mesh collider to be able to *contain* other objects this should be set to `false` */
115
117
  @serializable()
116
118
  convex: boolean = false;
117
119
 
src/engine/engine_gizmos.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { BufferAttribute, Line, BoxGeometry, EdgesGeometry, Color, LineSegments, LineBasicMaterial, Object3D, Mesh, SphereGeometry, type ColorRepresentation, Vector3, Box3, Quaternion, CylinderGeometry } from 'three';
1
+ import { BufferAttribute, Line, BoxGeometry, EdgesGeometry, Color, LineSegments, LineBasicMaterial, Object3D, Mesh, SphereGeometry, type ColorRepresentation, Vector3, Box3, Quaternion, CylinderGeometry, AxesHelper } from 'three';
2
2
  import { Context } from './engine_setup.js';
3
- import { setWorldPositionXYZ } from './engine_three_utils.js';
3
+ import { getWorldPosition, lookAtObject, setWorldPositionXYZ } from './engine_three_utils.js';
4
4
  import type { Vec3, Vec4 } from './engine_types.js';
5
+ import ThreeMeshUI, { Inline, Text } from "three-mesh-ui"
5
6
 
6
7
  const _tmp = new Vector3();
7
8
  const _tmp2 = new Vector3();
@@ -9,8 +10,27 @@
9
10
 
10
11
  const defaultColor: ColorRepresentation = 0x888888;
11
12
 
13
+ export type LabelHandle = {
14
+ setText(str: string);
15
+ }
16
+ declare type ColorWithAlpha = Color & { a: number };
17
+
12
18
  export class Gizmos {
13
19
 
20
+ /**
21
+ * Draw a label in the scene or attached to an object (if a parent is provided)
22
+ * @returns a handle to the label that can be used to change the text
23
+ */
24
+ static DrawLabel(position: Vec3, text: string, size: number = .1, duration: number = 9999, color?: ColorRepresentation, backgroundColor?: ColorRepresentation | ColorWithAlpha, parent?: Object3D,) {
25
+ if (!color) color = defaultColor;
26
+ const element = Internal.getTextLabel(duration, text, size, color, backgroundColor);
27
+ if (parent instanceof Object3D) parent.add(element);
28
+ element.position.x = position.x;
29
+ element.position.y = position.y;
30
+ element.position.z = position.z;
31
+ return element as LabelHandle;
32
+ }
33
+
14
34
  static DrawRay(origin: Vec3, dir: Vec3, color: ColorRepresentation = defaultColor, duration: number = 0, depthTest: boolean = true) {
15
35
  const obj = Internal.getLine(duration);
16
36
  const positions = obj.geometry.getAttribute("position");
@@ -126,6 +146,72 @@
126
146
  class Internal {
127
147
  // private static createdLines: number = 0;
128
148
 
149
+ private static readonly familyName = "needle-gizmos";
150
+ private static ensureFont() {
151
+ let fontFamily = ThreeMeshUI.FontLibrary.getFontFamily(this.familyName);
152
+
153
+ if (!fontFamily) {
154
+ fontFamily = ThreeMeshUI.FontLibrary.addFontFamily(this.familyName);
155
+ const variant = fontFamily.addVariant("normal", "normal", "./include/needle/arial-msdf.json", "./include/needle/arial.png") as any as ThreeMeshUI.FontVariant;
156
+ variant?.addEventListener('ready', () => {
157
+ ThreeMeshUI.update();
158
+ });
159
+ }
160
+ }
161
+
162
+ static getTextLabel(duration: number, text: string, size: number, color: ColorRepresentation, backgroundColor?: ColorRepresentation | ColorWithAlpha): Text & LabelHandle {
163
+ this.ensureFont();
164
+ let element = this.textLabelCache.pop();
165
+
166
+ let opacity = 1;
167
+ if (backgroundColor && typeof backgroundColor === "string" && backgroundColor?.length >= 8 && backgroundColor.startsWith("#")) {
168
+ opacity = parseInt(backgroundColor.substring(7), 16) / 255;
169
+ backgroundColor = backgroundColor.substring(0, 7);
170
+ console.log(backgroundColor, opacity);
171
+ }
172
+ else if (typeof backgroundColor === "object" && backgroundColor["a"] !== undefined) {
173
+ opacity = backgroundColor["a"]
174
+ }
175
+
176
+ if (!element) {
177
+ element = new Text({
178
+ boxSizing: 'border-box',
179
+ fontFamily: this.familyName,
180
+ width: "auto",
181
+ fontSize: size,
182
+ color: color,
183
+ lineHeight: .75,
184
+ backgroundColor: backgroundColor ?? undefined,
185
+ backgroundOpacity: opacity,
186
+ textContent: text,
187
+ borderRadius: .1,
188
+ padding: .1,
189
+ });
190
+ const global = this;
191
+ const labelHandle = element as LabelHandle & Text;
192
+ labelHandle.setText = function (str: string) {
193
+ this.set({ textContent: str });
194
+ global.tmuiNeedsUpdate = true;
195
+ };
196
+ }
197
+ else {
198
+ element.set({
199
+ color: color,
200
+ fontSize: size,
201
+ backgroundColor: backgroundColor ?? undefined,
202
+ backgroundOpacity: opacity,
203
+ textContent: text,
204
+ });
205
+ // const handle = element as any as LabelHandle;
206
+ // handle.setText(text);
207
+ }
208
+ this.tmuiNeedsUpdate = true;
209
+ element.layers.disableAll();
210
+ element.layers.enable(2);
211
+ this.registerTimedObject(Context.Current, element, duration, this.textLabelCache);
212
+ return element as Text & LabelHandle;
213
+ }
214
+
129
215
  static getBox(duration: number): Mesh {
130
216
  let box = this.boxesCache.pop();
131
217
  if (!box) {
@@ -174,6 +260,7 @@
174
260
  private static spheresCache: Mesh[] = [];
175
261
  private static boxesCache: Mesh[] = [];
176
262
  private static arrowHeadsCache: Mesh[] = [];
263
+ private static textLabelCache: Array<Text> = [];
177
264
 
178
265
  private static registerTimedObject(context: Context, object: Object3D, duration: number, cache: Array<Object3D>) {
179
266
  if (!this.contextPostRenderCallbacks.get(context)) {
@@ -181,6 +268,11 @@
181
268
  this.contextPostRenderCallbacks.set(context, cb);
182
269
  context.post_render_callbacks.push(cb);
183
270
  }
271
+ if (!this.contextBeforeRenderCallbacks.get(context)) {
272
+ const cb = () => { this.onBeforeRender(context, this.timedObjectsBuffer) };
273
+ this.contextBeforeRenderCallbacks.set(context, cb);
274
+ context.pre_render_callbacks.push(cb);
275
+ }
184
276
  object.layers.disableAll();
185
277
  object.layers.enable(2);
186
278
  object[$cacheSymbol] = cache;
@@ -193,15 +285,33 @@
193
285
  private static timedObjectsBuffer = new Array<Object3D>();
194
286
  private static timesBuffer = new Array<number>();
195
287
  private static contextPostRenderCallbacks = new Map<Context, () => void>();
288
+ private static contextBeforeRenderCallbacks = new Map<Context, () => void>();
289
+ private static tmuiNeedsUpdate = false;
196
290
 
291
+ private static onBeforeRender(ctx: Context, objects: Array<Object3D>) {
292
+ // const cameraWorldPosition = getWorldPosition(ctx.mainCamera!, _tmp);
293
+ if (this.tmuiNeedsUpdate) {
294
+ ThreeMeshUI.update();
295
+ }
296
+ for (let i = 0; i < objects.length; i++) {
297
+ const obj = objects[i];
298
+ if (ctx.mainCamera && obj instanceof ThreeMeshUI.MeshUIBaseElement) {
299
+ const isInXR = ctx.isInVR;
300
+ const keepUp = isInXR;
301
+ const copyRotation = !isInXR;
302
+ lookAtObject(obj, ctx.mainCamera, keepUp, copyRotation);
303
+ }
304
+ }
305
+ }
306
+
197
307
  private static onPostRender(ctx: Context, objects: Array<Object3D>, times: Array<number>) {
198
308
  const time = ctx.time.time;
199
309
  for (let i = 0; i < objects.length; i++) {
310
+ const obj = objects[i];
200
311
  if (time > times[i]) {
201
- const obj = objects[i];
202
312
  const cache = obj[$cacheSymbol];
203
- cache.push(obj as Line);
204
- ctx.scene.remove(obj);
313
+ cache.push(obj);
314
+ obj.removeFromParent();
205
315
  objects.splice(i, 1);
206
316
  times.splice(i, 1);
207
317
  }
src/engine/engine_physics_rapier.ts CHANGED
@@ -606,7 +606,7 @@
606
606
  this._meshCache.set(key, scaledPositions);
607
607
  }
608
608
  }
609
- const desc = convex ? ColliderDesc.convexMesh(positions) : ColliderDesc.trimesh(positions, indices);
609
+ const desc = convex ? ColliderDesc.convexHull(positions) : ColliderDesc.trimesh(positions, indices);
610
610
  if (desc) {
611
611
  const col = this.createCollider(collider, desc);
612
612
  // col.setMassProperties(1, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 });
src/engine/engine_three_utils.ts CHANGED
@@ -18,6 +18,33 @@
18
18
  obj.quaternion.multiply(flipYQuat);
19
19
  }
20
20
 
21
+
22
+ /** Better lookAt
23
+ * @param object the object that the lookAt should be applied to
24
+ * @param target the target to look at
25
+ * @param keepUpDirection if true the up direction will be kept
26
+ * @param copyTargetRotation if true the target rotation will be copied so the rotation is not skewed
27
+ */
28
+ export function lookAtObject(object: Object3D, target: Object3D, keepUpDirection: boolean = true, copyTargetRotation: boolean = false) {
29
+ const lookTarget = getWorldPosition(target);
30
+ const lookFrom = getWorldPosition(object);
31
+
32
+ if (copyTargetRotation) {
33
+ setWorldQuaternion(object, getWorldQuaternion(target));
34
+ // look at forward again so we don't get any roll
35
+ if (keepUpDirection) {
36
+ const forwardPoint = lookFrom.sub(getWorldDirection(object));
37
+ object.lookAt(forwardPoint);
38
+ }
39
+ return;
40
+ }
41
+
42
+ if (keepUpDirection) {
43
+ lookTarget.y = lookFrom.y;
44
+ }
45
+ object.lookAt(lookTarget);
46
+ }
47
+
21
48
  const _worldPositions = new CircularBuffer(() => new Vector3(), 100);
22
49
 
23
50
  export function getWorldPosition(obj: Object3D, vec: Vector3 | null = null, updateParents: boolean = true): Vector3 {
src/engine-components/utils/LookAt.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { serializable } from "../../engine/engine_serialization.js";
2
2
  import { Behaviour } from "../Component.js";
3
3
  import { Matrix4, Object3D, Quaternion, Vector3 } from "three";
4
- import { getWorldPosition, getWorldQuaternion, setWorldQuaternion } from "../../engine/engine_three_utils.js";
4
+ import { getWorldPosition, getWorldQuaternion, lookAtObject, setWorldQuaternion } from "../../engine/engine_three_utils.js";
5
5
 
6
6
  import { USDObject } from "../../engine-components/export/usdz/ThreeUSDZExporter.js";
7
7
  import { type UsdzBehaviour } from "../../engine-components/export/usdz/extensions/behavior/Behaviour.js";
@@ -27,25 +27,9 @@
27
27
  let target: Object3D | null | undefined = this.target;
28
28
  if (!target) target = this.context.mainCamera;
29
29
  if (!target) return;
30
-
31
- const lookTarget = getWorldPosition(target);
32
- const lookFrom = getWorldPosition(this.gameObject);
33
-
34
- if (this.keepUpDirection)
35
- lookTarget.y = lookFrom.y;
36
-
37
- if (this.copyTargetRotation) {
38
- setWorldQuaternion(this.gameObject, getWorldQuaternion(target));
39
30
 
40
- // look at forward again so we don't get any roll
41
- if (this.keepUpDirection) {
42
- const forwardPoint = lookFrom.sub(this.forward);
43
- this.gameObject.lookAt(forwardPoint);
44
- }
45
- }
46
- else
47
- this.gameObject.lookAt(lookTarget);
48
-
31
+ lookAtObject(this.gameObject, target, this.keepUpDirection, this.copyTargetRotation);
32
+
49
33
  if (this.invertForward)
50
34
  this.gameObject.quaternion.multiply(LookAt.flipYQuat);
51
35
  }
src/engine-components/webxr/WebXRPlaneTracking.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BufferAttribute, BufferGeometry, Group, Material, Mesh, Object3D, Vector3 } from "three";
1
+ import { Box3, BufferAttribute, BufferGeometry, Group, Material, Mesh, Object3D, Vector3 } from "three";
2
2
 
3
3
  import { MeshCollider } from "../Collider.js";
4
4
  import { Behaviour, GameObject } from "../Component.js";
@@ -7,6 +7,8 @@
7
7
  import type { Vec3 } from "../../engine/engine_types.js";
8
8
  import { disposeObjectResources } from "../../engine/engine_assetdatabase.js";
9
9
  import { getParam } from "../../engine/engine_utils.js";
10
+ import { destroy } from "../../engine/engine_gameobject.js";
11
+ // import { SimplifyModifier } from 'three/examples/jsm/modifiers/SimplifyModifier.js';
10
12
 
11
13
  const debug = getParam("debugplanetracking");
12
14
 
@@ -55,11 +57,13 @@
55
57
  get trackedMeshes() { return this._allMeshes.values(); }
56
58
 
57
59
  onEnable(): void {
60
+ WebXR.addEventListener(WebXREvent.XRStarted, this.onXRStarted);
58
61
  WebXR.addEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
59
62
  WebXR.addEventListener(WebXREvent.ModifyAROptions, this.onModifyAROptions);
60
63
  }
61
64
 
62
65
  onDisable(): void {
66
+ WebXR.removeEventListener(WebXREvent.XRStarted, this.onXRStarted);
63
67
  WebXR.removeEventListener(WebXREvent.XRUpdate, this.onXRUpdate);
64
68
  WebXR.removeEventListener(WebXREvent.ModifyAROptions, this.onModifyAROptions);
65
69
  }
@@ -76,6 +80,16 @@
76
80
  options.optionalFeatures = features;
77
81
  }
78
82
 
83
+ private onXRStarted = (_evt) => {
84
+ // remove all previously added data from the scene again
85
+ for (const data of this._allPlanes.keys()) {
86
+ this.removeData(data, this._allPlanes);
87
+ }
88
+ for (const data of this._allMeshes.keys()) {
89
+ this.removeData(data, this._allMeshes);
90
+ }
91
+ }
92
+
79
93
  private onXRUpdate = (evt) => {
80
94
 
81
95
  // parenting tracked planes to the XR rig ensures that they synced with the real-world user data;
@@ -118,8 +132,25 @@
118
132
  this.processFrameData(evt.rig, evt.frame, meshes, this._allMeshes);
119
133
  }
120
134
 
121
-
135
+ private removeData(data: XRPlane | XRMesh, _all: Map<XRPlane | XRMesh, XRPlaneContext>) {
136
+ const dataContext = _all.get(data);
137
+ if (!dataContext) return;
138
+ _all.delete(data);
139
+ if (debug) console.log("Plane no longer tracked, id=" + dataContext.id);
140
+ if (dataContext.mesh && dataContext.mesh.parent) {
141
+ dataContext.mesh.parent.remove(dataContext.mesh);
142
+ destroy(dataContext.mesh, true, true);
143
+ }
122
144
 
145
+ const evt = new CustomEvent<WebXRPlaneTrackingEvent>("plane-tracking", {
146
+ detail: {
147
+ type: "plane-removed",
148
+ context: dataContext
149
+ }
150
+ })
151
+ this.dispatchEvent(evt);
152
+ }
153
+
123
154
  private _dataId = 1;
124
155
  private readonly _allPlanes = new Map<XRPlane, XRPlaneContext>();
125
156
  private readonly _allMeshes = new Map<XRMesh, XRPlaneContext>();
@@ -132,20 +163,7 @@
132
163
 
133
164
  for (const data of _all.keys()) {
134
165
  if (!detected.has(data)) {
135
- const dataContext = _all.get(data)!;
136
- // plane was removed
137
- _all.delete(data);
138
- if (debug) console.log("Plane no longer tracked, id=" + dataContext.id);
139
- if (dataContext.mesh)
140
- rig?.remove(dataContext.mesh);
141
-
142
- const evt = new CustomEvent<WebXRPlaneTrackingEvent>("plane-tracking", {
143
- detail: {
144
- type: "plane-removed",
145
- context: dataContext
146
- }
147
- })
148
- this.dispatchEvent(evt);
166
+ this.removeData(data, _all);
149
167
  }
150
168
  }
151
169
 
@@ -202,11 +220,13 @@
202
220
 
203
221
  // Update the mesh collider if it exists
204
222
  if (planeContext.collider) {
205
- planeContext.collider.sharedMesh = planeContext.mesh as unknown as Mesh;
206
- planeContext.collider.convex = true;
223
+ const mesh = planeContext.mesh as unknown as Mesh;
224
+ planeContext.collider.sharedMesh = mesh;
225
+ planeContext.collider.convex = this.checkIfContextShouldBeConvex(mesh, planeContext.xrData);
207
226
  planeContext.collider.onDisable();
208
227
  planeContext.collider.onEnable();
209
228
  }
229
+ if (debug) console.log("Plane updated, id=" + planeContext.id, planeContext);
210
230
  }
211
231
 
212
232
  const evt = new CustomEvent<WebXRPlaneTrackingEvent>("plane-tracking", {
@@ -246,11 +266,11 @@
246
266
  }
247
267
  }
248
268
 
249
-
250
269
  const mc = newPlane.getComponent(MeshCollider) as MeshCollider;
251
270
  if (mc) {
252
- mc.sharedMesh = newPlane as unknown as Mesh;
253
- mc.convex = true;
271
+ const mesh = newPlane as unknown as Mesh;
272
+ mc.sharedMesh = mesh;
273
+ mc.convex = this.checkIfContextShouldBeConvex(mesh, data);
254
274
  mc.onDisable();
255
275
  mc.onEnable();
256
276
  }
@@ -271,7 +291,7 @@
271
291
  };
272
292
  _all.set(data, planeContext);
273
293
 
274
- if (debug) console.log("New plane detected, id=" + planeContext.id);
294
+ if (debug) console.log("New plane detected, id=" + planeContext.id, planeContext);
275
295
 
276
296
  try {
277
297
  const evt = new CustomEvent<WebXRPlaneTrackingEvent>("plane-tracking", {
@@ -299,6 +319,33 @@
299
319
  };
300
320
  }
301
321
 
322
+ // heuristic to determine if a collider should be convex or not -
323
+ // the "global mesh" should be non-convex, other meshes should be
324
+ checkIfContextShouldBeConvex(mesh: Mesh | Group | undefined, xrData: XRPlane | XRMesh) {
325
+ if (!mesh) return true;
326
+ if (mesh) {
327
+ // get bounding box of the mesh
328
+ const bbox = new Box3();
329
+ bbox.expandByObject(mesh);
330
+ const size = new Vector3();
331
+ bbox.getSize(size);
332
+
333
+ let isConvex = true;
334
+
335
+ // if the mesh is too big we make it non-convex
336
+ if (size.x > 2 && size.y > 2 && size.z > 1.5)
337
+ isConvex = false;
338
+
339
+ // if the semantic label is "wall" we make it convex
340
+ if (isConvex && "semanticLabel" in xrData && xrData.semanticLabel === "wall")
341
+ isConvex = true;
342
+
343
+ // console.log(size, xrData.semanticLabel, isConvex);
344
+ return isConvex;
345
+ }
346
+ return true;
347
+ }
348
+
302
349
  createGeometry(data: XRPlane | XRMesh) {
303
350
  if ("polygon" in data) {
304
351
  return this.createPlaneGeometry(data.polygon);
@@ -309,7 +356,14 @@
309
356
  return new BufferGeometry();
310
357
  }
311
358
 
359
+ // we cache vertices-to-geometry, because it looks like when we get an update sometimes the geometry stays the same.
360
+ // so we don't want to re-create the geometry every time.
361
+ private _verticesCache = new Map<string, BufferGeometry>();
312
362
  createMeshGeometry(vertices: Float32Array, indices: Uint32Array) {
363
+ const key = vertices.toString() + "_" + indices.toString();
364
+ if (this._verticesCache.has(key)) {
365
+ return this._verticesCache.get(key)!;
366
+ }
313
367
  const geometry = new BufferGeometry();
314
368
  geometry.setIndex(new BufferAttribute(indices, 1));
315
369
  geometry.setAttribute('position', new BufferAttribute(vertices, 3));
@@ -319,10 +373,19 @@
319
373
  uvs.push(vertices[i], vertices[i + 2]);
320
374
  }
321
375
  geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvs), 2));
376
+ // geometry.computeVertexNormals();
377
+ // geometry.computeTangents();
322
378
 
379
+ // simplify - too slow, would need to be on a worker it seems...
380
+ /*
381
+ const modifier = new SimplifyModifier();
382
+ const simplified = modifier.modify(geometry, Math.floor(indices.length / 3) * 0.1);
383
+ geometry.dispose();
384
+ geometry.copy(simplified);
323
385
  geometry.computeVertexNormals();
324
- // geometry.computeTangents();
325
-
386
+ */
387
+
388
+ this._verticesCache.set(key, geometry);
326
389
  return geometry;
327
390
  }
328
391
 
src/include/needle/arial-msdf.json ADDED
@@ -0,0 +1,1472 @@
1
+ {
2
+ "pages": [
3
+ "arial.png"
4
+ ],
5
+ "chars": [
6
+ {
7
+ "id": 40,
8
+ "index": 11,
9
+ "char": "(",
10
+ "width": 14,
11
+ "height": 43,
12
+ "xoffset": 1,
13
+ "yoffset": 2,
14
+ "xadvance": 14,
15
+ "chnl": 15,
16
+ "x": 0,
17
+ "y": 0,
18
+ "page": 0
19
+ },
20
+ {
21
+ "id": 41,
22
+ "index": 12,
23
+ "char": ")",
24
+ "width": 14,
25
+ "height": 43,
26
+ "xoffset": 1,
27
+ "yoffset": 2,
28
+ "xadvance": 14,
29
+ "chnl": 15,
30
+ "x": 15,
31
+ "y": 0,
32
+ "page": 0
33
+ },
34
+ {
35
+ "id": 64,
36
+ "index": 35,
37
+ "char": "@",
38
+ "width": 43,
39
+ "height": 43,
40
+ "xoffset": 0,
41
+ "yoffset": 2,
42
+ "xadvance": 43,
43
+ "chnl": 15,
44
+ "x": 30,
45
+ "y": 0,
46
+ "page": 0
47
+ },
48
+ {
49
+ "id": 87,
50
+ "index": 58,
51
+ "char": "W",
52
+ "width": 43,
53
+ "height": 34,
54
+ "xoffset": -1,
55
+ "yoffset": 3,
56
+ "xadvance": 40,
57
+ "chnl": 15,
58
+ "x": 0,
59
+ "y": 44,
60
+ "page": 0
61
+ },
62
+ {
63
+ "id": 106,
64
+ "index": 77,
65
+ "char": "j",
66
+ "width": 12,
67
+ "height": 43,
68
+ "xoffset": -4,
69
+ "yoffset": 3,
70
+ "xadvance": 9,
71
+ "chnl": 15,
72
+ "x": 74,
73
+ "y": 0,
74
+ "page": 0
75
+ },
76
+ {
77
+ "id": 123,
78
+ "index": 94,
79
+ "char": "{",
80
+ "width": 16,
81
+ "height": 43,
82
+ "xoffset": -1,
83
+ "yoffset": 2,
84
+ "xadvance": 14,
85
+ "chnl": 15,
86
+ "x": 0,
87
+ "y": 79,
88
+ "page": 0
89
+ },
90
+ {
91
+ "id": 124,
92
+ "index": 95,
93
+ "char": "|",
94
+ "width": 7,
95
+ "height": 43,
96
+ "xoffset": 2,
97
+ "yoffset": 2,
98
+ "xadvance": 11,
99
+ "chnl": 15,
100
+ "x": 17,
101
+ "y": 79,
102
+ "page": 0
103
+ },
104
+ {
105
+ "id": 125,
106
+ "index": 96,
107
+ "char": "}",
108
+ "width": 16,
109
+ "height": 43,
110
+ "xoffset": -1,
111
+ "yoffset": 2,
112
+ "xadvance": 14,
113
+ "chnl": 15,
114
+ "x": 25,
115
+ "y": 79,
116
+ "page": 0
117
+ },
118
+ {
119
+ "id": 91,
120
+ "index": 62,
121
+ "char": "[",
122
+ "width": 12,
123
+ "height": 42,
124
+ "xoffset": 1,
125
+ "yoffset": 3,
126
+ "xadvance": 12,
127
+ "chnl": 15,
128
+ "x": 42,
129
+ "y": 79,
130
+ "page": 0
131
+ },
132
+ {
133
+ "id": 93,
134
+ "index": 64,
135
+ "char": "]",
136
+ "width": 12,
137
+ "height": 42,
138
+ "xoffset": -1,
139
+ "yoffset": 3,
140
+ "xadvance": 12,
141
+ "chnl": 15,
142
+ "x": 55,
143
+ "y": 44,
144
+ "page": 0
145
+ },
146
+ {
147
+ "id": 36,
148
+ "index": 7,
149
+ "char": "$",
150
+ "width": 24,
151
+ "height": 41,
152
+ "xoffset": -1,
153
+ "yoffset": 0,
154
+ "xadvance": 23,
155
+ "chnl": 15,
156
+ "x": 87,
157
+ "y": 0,
158
+ "page": 0
159
+ },
160
+ {
161
+ "id": 81,
162
+ "index": 52,
163
+ "char": "Q",
164
+ "width": 33,
165
+ "height": 37,
166
+ "xoffset": 0,
167
+ "yoffset": 2,
168
+ "xadvance": 33,
169
+ "chnl": 15,
170
+ "x": 68,
171
+ "y": 44,
172
+ "page": 0
173
+ },
174
+ {
175
+ "id": 37,
176
+ "index": 8,
177
+ "char": "%",
178
+ "width": 36,
179
+ "height": 36,
180
+ "xoffset": 0,
181
+ "yoffset": 2,
182
+ "xadvance": 37,
183
+ "chnl": 15,
184
+ "x": 68,
185
+ "y": 82,
186
+ "page": 0
187
+ },
188
+ {
189
+ "id": 35,
190
+ "index": 6,
191
+ "char": "#",
192
+ "width": 26,
193
+ "height": 35,
194
+ "xoffset": -2,
195
+ "yoffset": 2,
196
+ "xadvance": 23,
197
+ "chnl": 15,
198
+ "x": 112,
199
+ "y": 0,
200
+ "page": 0
201
+ },
202
+ {
203
+ "id": 38,
204
+ "index": 9,
205
+ "char": "&",
206
+ "width": 29,
207
+ "height": 35,
208
+ "xoffset": 0,
209
+ "yoffset": 2,
210
+ "xadvance": 28,
211
+ "chnl": 15,
212
+ "x": 102,
213
+ "y": 42,
214
+ "page": 0
215
+ },
216
+ {
217
+ "id": 47,
218
+ "index": 18,
219
+ "char": "/",
220
+ "width": 16,
221
+ "height": 35,
222
+ "xoffset": -2,
223
+ "yoffset": 2,
224
+ "xadvance": 12,
225
+ "chnl": 15,
226
+ "x": 105,
227
+ "y": 78,
228
+ "page": 0
229
+ },
230
+ {
231
+ "id": 48,
232
+ "index": 19,
233
+ "char": "0",
234
+ "width": 24,
235
+ "height": 35,
236
+ "xoffset": 0,
237
+ "yoffset": 3,
238
+ "xadvance": 23,
239
+ "chnl": 15,
240
+ "x": 0,
241
+ "y": 123,
242
+ "page": 0
243
+ },
244
+ {
245
+ "id": 51,
246
+ "index": 22,
247
+ "char": "3",
248
+ "width": 24,
249
+ "height": 35,
250
+ "xoffset": 0,
251
+ "yoffset": 3,
252
+ "xadvance": 23,
253
+ "chnl": 15,
254
+ "x": 25,
255
+ "y": 123,
256
+ "page": 0
257
+ },
258
+ {
259
+ "id": 54,
260
+ "index": 25,
261
+ "char": "6",
262
+ "width": 24,
263
+ "height": 35,
264
+ "xoffset": 0,
265
+ "yoffset": 3,
266
+ "xadvance": 23,
267
+ "chnl": 15,
268
+ "x": 50,
269
+ "y": 122,
270
+ "page": 0
271
+ },
272
+ {
273
+ "id": 56,
274
+ "index": 27,
275
+ "char": "8",
276
+ "width": 24,
277
+ "height": 35,
278
+ "xoffset": 0,
279
+ "yoffset": 3,
280
+ "xadvance": 23,
281
+ "chnl": 15,
282
+ "x": 75,
283
+ "y": 119,
284
+ "page": 0
285
+ },
286
+ {
287
+ "id": 57,
288
+ "index": 28,
289
+ "char": "9",
290
+ "width": 24,
291
+ "height": 35,
292
+ "xoffset": 0,
293
+ "yoffset": 3,
294
+ "xadvance": 23,
295
+ "chnl": 15,
296
+ "x": 100,
297
+ "y": 119,
298
+ "page": 0
299
+ },
300
+ {
301
+ "id": 63,
302
+ "index": 34,
303
+ "char": "?",
304
+ "width": 23,
305
+ "height": 35,
306
+ "xoffset": 0,
307
+ "yoffset": 2,
308
+ "xadvance": 23,
309
+ "chnl": 15,
310
+ "x": 139,
311
+ "y": 0,
312
+ "page": 0
313
+ },
314
+ {
315
+ "id": 67,
316
+ "index": 38,
317
+ "char": "C",
318
+ "width": 31,
319
+ "height": 35,
320
+ "xoffset": 0,
321
+ "yoffset": 2,
322
+ "xadvance": 30,
323
+ "chnl": 15,
324
+ "x": 122,
325
+ "y": 78,
326
+ "page": 0
327
+ },
328
+ {
329
+ "id": 71,
330
+ "index": 42,
331
+ "char": "G",
332
+ "width": 32,
333
+ "height": 35,
334
+ "xoffset": 0,
335
+ "yoffset": 2,
336
+ "xadvance": 33,
337
+ "chnl": 15,
338
+ "x": 125,
339
+ "y": 114,
340
+ "page": 0
341
+ },
342
+ {
343
+ "id": 74,
344
+ "index": 45,
345
+ "char": "J",
346
+ "width": 21,
347
+ "height": 35,
348
+ "xoffset": -1,
349
+ "yoffset": 3,
350
+ "xadvance": 21,
351
+ "chnl": 15,
352
+ "x": 132,
353
+ "y": 36,
354
+ "page": 0
355
+ },
356
+ {
357
+ "id": 79,
358
+ "index": 50,
359
+ "char": "O",
360
+ "width": 33,
361
+ "height": 35,
362
+ "xoffset": 0,
363
+ "yoffset": 2,
364
+ "xadvance": 33,
365
+ "chnl": 15,
366
+ "x": 0,
367
+ "y": 159,
368
+ "page": 0
369
+ },
370
+ {
371
+ "id": 83,
372
+ "index": 54,
373
+ "char": "S",
374
+ "width": 28,
375
+ "height": 35,
376
+ "xoffset": 0,
377
+ "yoffset": 2,
378
+ "xadvance": 28,
379
+ "chnl": 15,
380
+ "x": 34,
381
+ "y": 159,
382
+ "page": 0
383
+ },
384
+ {
385
+ "id": 85,
386
+ "index": 56,
387
+ "char": "U",
388
+ "width": 28,
389
+ "height": 35,
390
+ "xoffset": 1,
391
+ "yoffset": 3,
392
+ "xadvance": 30,
393
+ "chnl": 15,
394
+ "x": 63,
395
+ "y": 158,
396
+ "page": 0
397
+ },
398
+ {
399
+ "id": 92,
400
+ "index": 63,
401
+ "char": "\\",
402
+ "width": 16,
403
+ "height": 35,
404
+ "xoffset": -2,
405
+ "yoffset": 2,
406
+ "xadvance": 12,
407
+ "chnl": 15,
408
+ "x": 92,
409
+ "y": 155,
410
+ "page": 0
411
+ },
412
+ {
413
+ "id": 98,
414
+ "index": 69,
415
+ "char": "b",
416
+ "width": 23,
417
+ "height": 35,
418
+ "xoffset": 1,
419
+ "yoffset": 3,
420
+ "xadvance": 23,
421
+ "chnl": 15,
422
+ "x": 109,
423
+ "y": 155,
424
+ "page": 0
425
+ },
426
+ {
427
+ "id": 100,
428
+ "index": 71,
429
+ "char": "d",
430
+ "width": 23,
431
+ "height": 35,
432
+ "xoffset": -1,
433
+ "yoffset": 3,
434
+ "xadvance": 23,
435
+ "chnl": 15,
436
+ "x": 133,
437
+ "y": 150,
438
+ "page": 0
439
+ },
440
+ {
441
+ "id": 102,
442
+ "index": 73,
443
+ "char": "f",
444
+ "width": 17,
445
+ "height": 35,
446
+ "xoffset": -2,
447
+ "yoffset": 2,
448
+ "xadvance": 12,
449
+ "chnl": 15,
450
+ "x": 163,
451
+ "y": 0,
452
+ "page": 0
453
+ },
454
+ {
455
+ "id": 103,
456
+ "index": 74,
457
+ "char": "g",
458
+ "width": 23,
459
+ "height": 35,
460
+ "xoffset": -1,
461
+ "yoffset": 11,
462
+ "xadvance": 23,
463
+ "chnl": 15,
464
+ "x": 157,
465
+ "y": 150,
466
+ "page": 0
467
+ },
468
+ {
469
+ "id": 112,
470
+ "index": 83,
471
+ "char": "p",
472
+ "width": 23,
473
+ "height": 35,
474
+ "xoffset": 1,
475
+ "yoffset": 11,
476
+ "xadvance": 23,
477
+ "chnl": 15,
478
+ "x": 154,
479
+ "y": 36,
480
+ "page": 0
481
+ },
482
+ {
483
+ "id": 113,
484
+ "index": 84,
485
+ "char": "q",
486
+ "width": 23,
487
+ "height": 35,
488
+ "xoffset": -1,
489
+ "yoffset": 11,
490
+ "xadvance": 23,
491
+ "chnl": 15,
492
+ "x": 154,
493
+ "y": 72,
494
+ "page": 0
495
+ },
496
+ {
497
+ "id": 121,
498
+ "index": 92,
499
+ "char": "y",
500
+ "width": 24,
501
+ "height": 35,
502
+ "xoffset": -1,
503
+ "yoffset": 11,
504
+ "xadvance": 21,
505
+ "chnl": 15,
506
+ "x": 181,
507
+ "y": 0,
508
+ "page": 0
509
+ },
510
+ {
511
+ "id": 78,
512
+ "index": 49,
513
+ "char": "N",
514
+ "width": 28,
515
+ "height": 34,
516
+ "xoffset": 1,
517
+ "yoffset": 3,
518
+ "xadvance": 30,
519
+ "chnl": 15,
520
+ "x": 158,
521
+ "y": 108,
522
+ "page": 0
523
+ },
524
+ {
525
+ "id": 84,
526
+ "index": 55,
527
+ "char": "T",
528
+ "width": 28,
529
+ "height": 34,
530
+ "xoffset": -1,
531
+ "yoffset": 3,
532
+ "xadvance": 26,
533
+ "chnl": 15,
534
+ "x": 0,
535
+ "y": 195,
536
+ "page": 0
537
+ },
538
+ {
539
+ "id": 116,
540
+ "index": 87,
541
+ "char": "t",
542
+ "width": 15,
543
+ "height": 34,
544
+ "xoffset": -1,
545
+ "yoffset": 4,
546
+ "xadvance": 12,
547
+ "chnl": 15,
548
+ "x": 29,
549
+ "y": 195,
550
+ "page": 0
551
+ },
552
+ {
553
+ "id": 119,
554
+ "index": 90,
555
+ "char": "w",
556
+ "width": 34,
557
+ "height": 26,
558
+ "xoffset": -2,
559
+ "yoffset": 11,
560
+ "xadvance": 30,
561
+ "chnl": 15,
562
+ "x": 45,
563
+ "y": 195,
564
+ "page": 0
565
+ },
566
+ {
567
+ "id": 33,
568
+ "index": 4,
569
+ "char": "!",
570
+ "width": 9,
571
+ "height": 34,
572
+ "xoffset": 2,
573
+ "yoffset": 3,
574
+ "xadvance": 12,
575
+ "chnl": 15,
576
+ "x": 44,
577
+ "y": 44,
578
+ "page": 0
579
+ },
580
+ {
581
+ "id": 49,
582
+ "index": 20,
583
+ "char": "1",
584
+ "width": 15,
585
+ "height": 34,
586
+ "xoffset": 3,
587
+ "yoffset": 3,
588
+ "xadvance": 23,
589
+ "chnl": 15,
590
+ "x": 80,
591
+ "y": 194,
592
+ "page": 0
593
+ },
594
+ {
595
+ "id": 50,
596
+ "index": 21,
597
+ "char": "2",
598
+ "width": 24,
599
+ "height": 34,
600
+ "xoffset": -1,
601
+ "yoffset": 3,
602
+ "xadvance": 23,
603
+ "chnl": 15,
604
+ "x": 181,
605
+ "y": 143,
606
+ "page": 0
607
+ },
608
+ {
609
+ "id": 52,
610
+ "index": 23,
611
+ "char": "4",
612
+ "width": 25,
613
+ "height": 34,
614
+ "xoffset": -1,
615
+ "yoffset": 3,
616
+ "xadvance": 23,
617
+ "chnl": 15,
618
+ "x": 178,
619
+ "y": 36,
620
+ "page": 0
621
+ },
622
+ {
623
+ "id": 53,
624
+ "index": 24,
625
+ "char": "5",
626
+ "width": 24,
627
+ "height": 34,
628
+ "xoffset": 0,
629
+ "yoffset": 3,
630
+ "xadvance": 23,
631
+ "chnl": 15,
632
+ "x": 181,
633
+ "y": 178,
634
+ "page": 0
635
+ },
636
+ {
637
+ "id": 55,
638
+ "index": 26,
639
+ "char": "7",
640
+ "width": 23,
641
+ "height": 34,
642
+ "xoffset": 0,
643
+ "yoffset": 3,
644
+ "xadvance": 23,
645
+ "chnl": 15,
646
+ "x": 178,
647
+ "y": 71,
648
+ "page": 0
649
+ },
650
+ {
651
+ "id": 65,
652
+ "index": 36,
653
+ "char": "A",
654
+ "width": 32,
655
+ "height": 34,
656
+ "xoffset": -2,
657
+ "yoffset": 3,
658
+ "xadvance": 28,
659
+ "chnl": 15,
660
+ "x": 96,
661
+ "y": 191,
662
+ "page": 0
663
+ },
664
+ {
665
+ "id": 66,
666
+ "index": 37,
667
+ "char": "B",
668
+ "width": 27,
669
+ "height": 34,
670
+ "xoffset": 1,
671
+ "yoffset": 3,
672
+ "xadvance": 28,
673
+ "chnl": 15,
674
+ "x": 129,
675
+ "y": 191,
676
+ "page": 0
677
+ },
678
+ {
679
+ "id": 68,
680
+ "index": 39,
681
+ "char": "D",
682
+ "width": 29,
683
+ "height": 34,
684
+ "xoffset": 1,
685
+ "yoffset": 3,
686
+ "xadvance": 30,
687
+ "chnl": 15,
688
+ "x": 206,
689
+ "y": 0,
690
+ "page": 0
691
+ },
692
+ {
693
+ "id": 69,
694
+ "index": 40,
695
+ "char": "E",
696
+ "width": 26,
697
+ "height": 34,
698
+ "xoffset": 1,
699
+ "yoffset": 3,
700
+ "xadvance": 28,
701
+ "chnl": 15,
702
+ "x": 187,
703
+ "y": 106,
704
+ "page": 0
705
+ },
706
+ {
707
+ "id": 70,
708
+ "index": 41,
709
+ "char": "F",
710
+ "width": 24,
711
+ "height": 34,
712
+ "xoffset": 1,
713
+ "yoffset": 3,
714
+ "xadvance": 26,
715
+ "chnl": 15,
716
+ "x": 202,
717
+ "y": 71,
718
+ "page": 0
719
+ },
720
+ {
721
+ "id": 72,
722
+ "index": 43,
723
+ "char": "H",
724
+ "width": 28,
725
+ "height": 34,
726
+ "xoffset": 1,
727
+ "yoffset": 3,
728
+ "xadvance": 30,
729
+ "chnl": 15,
730
+ "x": 204,
731
+ "y": 36,
732
+ "page": 0
733
+ },
734
+ {
735
+ "id": 73,
736
+ "index": 44,
737
+ "char": "I",
738
+ "width": 8,
739
+ "height": 34,
740
+ "xoffset": 2,
741
+ "yoffset": 3,
742
+ "xadvance": 12,
743
+ "chnl": 15,
744
+ "x": 55,
745
+ "y": 87,
746
+ "page": 0
747
+ },
748
+ {
749
+ "id": 75,
750
+ "index": 46,
751
+ "char": "K",
752
+ "width": 29,
753
+ "height": 34,
754
+ "xoffset": 1,
755
+ "yoffset": 3,
756
+ "xadvance": 28,
757
+ "chnl": 15,
758
+ "x": 206,
759
+ "y": 141,
760
+ "page": 0
761
+ },
762
+ {
763
+ "id": 76,
764
+ "index": 47,
765
+ "char": "L",
766
+ "width": 23,
767
+ "height": 34,
768
+ "xoffset": 1,
769
+ "yoffset": 3,
770
+ "xadvance": 23,
771
+ "chnl": 15,
772
+ "x": 157,
773
+ "y": 186,
774
+ "page": 0
775
+ },
776
+ {
777
+ "id": 77,
778
+ "index": 48,
779
+ "char": "M",
780
+ "width": 33,
781
+ "height": 34,
782
+ "xoffset": 1,
783
+ "yoffset": 3,
784
+ "xadvance": 35,
785
+ "chnl": 15,
786
+ "x": 0,
787
+ "y": 230,
788
+ "page": 0
789
+ },
790
+ {
791
+ "id": 80,
792
+ "index": 51,
793
+ "char": "P",
794
+ "width": 27,
795
+ "height": 34,
796
+ "xoffset": 1,
797
+ "yoffset": 3,
798
+ "xadvance": 28,
799
+ "chnl": 15,
800
+ "x": 34,
801
+ "y": 230,
802
+ "page": 0
803
+ },
804
+ {
805
+ "id": 82,
806
+ "index": 53,
807
+ "char": "R",
808
+ "width": 30,
809
+ "height": 34,
810
+ "xoffset": 1,
811
+ "yoffset": 3,
812
+ "xadvance": 30,
813
+ "chnl": 15,
814
+ "x": 62,
815
+ "y": 229,
816
+ "page": 0
817
+ },
818
+ {
819
+ "id": 86,
820
+ "index": 57,
821
+ "char": "V",
822
+ "width": 32,
823
+ "height": 34,
824
+ "xoffset": -2,
825
+ "yoffset": 3,
826
+ "xadvance": 28,
827
+ "chnl": 15,
828
+ "x": 93,
829
+ "y": 229,
830
+ "page": 0
831
+ },
832
+ {
833
+ "id": 88,
834
+ "index": 59,
835
+ "char": "X",
836
+ "width": 32,
837
+ "height": 34,
838
+ "xoffset": -2,
839
+ "yoffset": 3,
840
+ "xadvance": 28,
841
+ "chnl": 15,
842
+ "x": 126,
843
+ "y": 226,
844
+ "page": 0
845
+ },
846
+ {
847
+ "id": 89,
848
+ "index": 60,
849
+ "char": "Y",
850
+ "width": 32,
851
+ "height": 34,
852
+ "xoffset": -2,
853
+ "yoffset": 3,
854
+ "xadvance": 28,
855
+ "chnl": 15,
856
+ "x": 159,
857
+ "y": 221,
858
+ "page": 0
859
+ },
860
+ {
861
+ "id": 90,
862
+ "index": 61,
863
+ "char": "Z",
864
+ "width": 28,
865
+ "height": 34,
866
+ "xoffset": -1,
867
+ "yoffset": 3,
868
+ "xadvance": 26,
869
+ "chnl": 15,
870
+ "x": 206,
871
+ "y": 176,
872
+ "page": 0
873
+ },
874
+ {
875
+ "id": 104,
876
+ "index": 75,
877
+ "char": "h",
878
+ "width": 22,
879
+ "height": 34,
880
+ "xoffset": 1,
881
+ "yoffset": 3,
882
+ "xadvance": 23,
883
+ "chnl": 15,
884
+ "x": 206,
885
+ "y": 211,
886
+ "page": 0
887
+ },
888
+ {
889
+ "id": 105,
890
+ "index": 76,
891
+ "char": "i",
892
+ "width": 8,
893
+ "height": 34,
894
+ "xoffset": 1,
895
+ "yoffset": 3,
896
+ "xadvance": 9,
897
+ "chnl": 15,
898
+ "x": 214,
899
+ "y": 106,
900
+ "page": 0
901
+ },
902
+ {
903
+ "id": 107,
904
+ "index": 78,
905
+ "char": "k",
906
+ "width": 22,
907
+ "height": 34,
908
+ "xoffset": 1,
909
+ "yoffset": 3,
910
+ "xadvance": 21,
911
+ "chnl": 15,
912
+ "x": 223,
913
+ "y": 106,
914
+ "page": 0
915
+ },
916
+ {
917
+ "id": 108,
918
+ "index": 79,
919
+ "char": "l",
920
+ "width": 8,
921
+ "height": 34,
922
+ "xoffset": 1,
923
+ "yoffset": 3,
924
+ "xadvance": 9,
925
+ "chnl": 15,
926
+ "x": 227,
927
+ "y": 71,
928
+ "page": 0
929
+ },
930
+ {
931
+ "id": 109,
932
+ "index": 80,
933
+ "char": "m",
934
+ "width": 34,
935
+ "height": 26,
936
+ "xoffset": 1,
937
+ "yoffset": 11,
938
+ "xadvance": 35,
939
+ "chnl": 15,
940
+ "x": 233,
941
+ "y": 35,
942
+ "page": 0
943
+ },
944
+ {
945
+ "id": 59,
946
+ "index": 30,
947
+ "char": ";",
948
+ "width": 8,
949
+ "height": 32,
950
+ "xoffset": 1,
951
+ "yoffset": 11,
952
+ "xadvance": 12,
953
+ "chnl": 15,
954
+ "x": 236,
955
+ "y": 0,
956
+ "page": 0
957
+ },
958
+ {
959
+ "id": 95,
960
+ "index": 66,
961
+ "char": "_",
962
+ "width": 28,
963
+ "height": 7,
964
+ "xoffset": -3,
965
+ "yoffset": 39,
966
+ "xadvance": 23,
967
+ "chnl": 15,
968
+ "x": 159,
969
+ "y": 256,
970
+ "page": 0
971
+ },
972
+ {
973
+ "id": 101,
974
+ "index": 72,
975
+ "char": "e",
976
+ "width": 24,
977
+ "height": 27,
978
+ "xoffset": 0,
979
+ "yoffset": 11,
980
+ "xadvance": 23,
981
+ "chnl": 15,
982
+ "x": 268,
983
+ "y": 0,
984
+ "page": 0
985
+ },
986
+ {
987
+ "id": 97,
988
+ "index": 68,
989
+ "char": "a",
990
+ "width": 24,
991
+ "height": 27,
992
+ "xoffset": 0,
993
+ "yoffset": 11,
994
+ "xadvance": 23,
995
+ "chnl": 15,
996
+ "x": 268,
997
+ "y": 28,
998
+ "page": 0
999
+ },
1000
+ {
1001
+ "id": 99,
1002
+ "index": 70,
1003
+ "char": "c",
1004
+ "width": 23,
1005
+ "height": 27,
1006
+ "xoffset": 0,
1007
+ "yoffset": 11,
1008
+ "xadvance": 21,
1009
+ "chnl": 15,
1010
+ "x": 268,
1011
+ "y": 56,
1012
+ "page": 0
1013
+ },
1014
+ {
1015
+ "id": 111,
1016
+ "index": 82,
1017
+ "char": "o",
1018
+ "width": 24,
1019
+ "height": 27,
1020
+ "xoffset": -1,
1021
+ "yoffset": 11,
1022
+ "xadvance": 23,
1023
+ "chnl": 15,
1024
+ "x": 236,
1025
+ "y": 62,
1026
+ "page": 0
1027
+ },
1028
+ {
1029
+ "id": 115,
1030
+ "index": 86,
1031
+ "char": "s",
1032
+ "width": 22,
1033
+ "height": 27,
1034
+ "xoffset": -1,
1035
+ "yoffset": 11,
1036
+ "xadvance": 21,
1037
+ "chnl": 15,
1038
+ "x": 245,
1039
+ "y": 0,
1040
+ "page": 0
1041
+ },
1042
+ {
1043
+ "id": 120,
1044
+ "index": 91,
1045
+ "char": "x",
1046
+ "width": 24,
1047
+ "height": 26,
1048
+ "xoffset": -2,
1049
+ "yoffset": 11,
1050
+ "xadvance": 21,
1051
+ "chnl": 15,
1052
+ "x": 261,
1053
+ "y": 84,
1054
+ "page": 0
1055
+ },
1056
+ {
1057
+ "id": 58,
1058
+ "index": 29,
1059
+ "char": ":",
1060
+ "width": 8,
1061
+ "height": 26,
1062
+ "xoffset": 2,
1063
+ "yoffset": 11,
1064
+ "xadvance": 12,
1065
+ "chnl": 15,
1066
+ "x": 192,
1067
+ "y": 213,
1068
+ "page": 0
1069
+ },
1070
+ {
1071
+ "id": 110,
1072
+ "index": 81,
1073
+ "char": "n",
1074
+ "width": 22,
1075
+ "height": 26,
1076
+ "xoffset": 1,
1077
+ "yoffset": 11,
1078
+ "xadvance": 23,
1079
+ "chnl": 15,
1080
+ "x": 246,
1081
+ "y": 111,
1082
+ "page": 0
1083
+ },
1084
+ {
1085
+ "id": 114,
1086
+ "index": 85,
1087
+ "char": "r",
1088
+ "width": 16,
1089
+ "height": 26,
1090
+ "xoffset": 1,
1091
+ "yoffset": 11,
1092
+ "xadvance": 14,
1093
+ "chnl": 15,
1094
+ "x": 269,
1095
+ "y": 111,
1096
+ "page": 0
1097
+ },
1098
+ {
1099
+ "id": 117,
1100
+ "index": 88,
1101
+ "char": "u",
1102
+ "width": 22,
1103
+ "height": 26,
1104
+ "xoffset": 1,
1105
+ "yoffset": 11,
1106
+ "xadvance": 23,
1107
+ "chnl": 15,
1108
+ "x": 246,
1109
+ "y": 138,
1110
+ "page": 0
1111
+ },
1112
+ {
1113
+ "id": 118,
1114
+ "index": 89,
1115
+ "char": "v",
1116
+ "width": 24,
1117
+ "height": 26,
1118
+ "xoffset": -1,
1119
+ "yoffset": 11,
1120
+ "xadvance": 21,
1121
+ "chnl": 15,
1122
+ "x": 269,
1123
+ "y": 138,
1124
+ "page": 0
1125
+ },
1126
+ {
1127
+ "id": 122,
1128
+ "index": 93,
1129
+ "char": "z",
1130
+ "width": 23,
1131
+ "height": 26,
1132
+ "xoffset": -1,
1133
+ "yoffset": 11,
1134
+ "xadvance": 21,
1135
+ "chnl": 15,
1136
+ "x": 229,
1137
+ "y": 211,
1138
+ "page": 0
1139
+ },
1140
+ {
1141
+ "id": 126,
1142
+ "index": 97,
1143
+ "char": "~",
1144
+ "width": 25,
1145
+ "height": 11,
1146
+ "xoffset": 0,
1147
+ "yoffset": 15,
1148
+ "xadvance": 25,
1149
+ "chnl": 15,
1150
+ "x": 192,
1151
+ "y": 246,
1152
+ "page": 0
1153
+ },
1154
+ {
1155
+ "id": 43,
1156
+ "index": 14,
1157
+ "char": "+",
1158
+ "width": 24,
1159
+ "height": 24,
1160
+ "xoffset": 0,
1161
+ "yoffset": 8,
1162
+ "xadvance": 25,
1163
+ "chnl": 15,
1164
+ "x": 229,
1165
+ "y": 238,
1166
+ "page": 0
1167
+ },
1168
+ {
1169
+ "id": 60,
1170
+ "index": 31,
1171
+ "char": "<",
1172
+ "width": 24,
1173
+ "height": 24,
1174
+ "xoffset": 0,
1175
+ "yoffset": 8,
1176
+ "xadvance": 25,
1177
+ "chnl": 15,
1178
+ "x": 235,
1179
+ "y": 176,
1180
+ "page": 0
1181
+ },
1182
+ {
1183
+ "id": 61,
1184
+ "index": 32,
1185
+ "char": "=",
1186
+ "width": 24,
1187
+ "height": 17,
1188
+ "xoffset": 0,
1189
+ "yoffset": 12,
1190
+ "xadvance": 25,
1191
+ "chnl": 15,
1192
+ "x": 260,
1193
+ "y": 165,
1194
+ "page": 0
1195
+ },
1196
+ {
1197
+ "id": 62,
1198
+ "index": 33,
1199
+ "char": ">",
1200
+ "width": 24,
1201
+ "height": 24,
1202
+ "xoffset": 0,
1203
+ "yoffset": 8,
1204
+ "xadvance": 25,
1205
+ "chnl": 15,
1206
+ "x": 260,
1207
+ "y": 183,
1208
+ "page": 0
1209
+ },
1210
+ {
1211
+ "id": 94,
1212
+ "index": 65,
1213
+ "char": "^",
1214
+ "width": 21,
1215
+ "height": 20,
1216
+ "xoffset": -1,
1217
+ "yoffset": 2,
1218
+ "xadvance": 20,
1219
+ "chnl": 15,
1220
+ "x": 253,
1221
+ "y": 208,
1222
+ "page": 0
1223
+ },
1224
+ {
1225
+ "id": 42,
1226
+ "index": 13,
1227
+ "char": "*",
1228
+ "width": 18,
1229
+ "height": 17,
1230
+ "xoffset": -1,
1231
+ "yoffset": 2,
1232
+ "xadvance": 16,
1233
+ "chnl": 15,
1234
+ "x": 275,
1235
+ "y": 208,
1236
+ "page": 0
1237
+ },
1238
+ {
1239
+ "id": 34,
1240
+ "index": 5,
1241
+ "char": "\"",
1242
+ "width": 15,
1243
+ "height": 15,
1244
+ "xoffset": 0,
1245
+ "yoffset": 3,
1246
+ "xadvance": 15,
1247
+ "chnl": 15,
1248
+ "x": 236,
1249
+ "y": 90,
1250
+ "page": 0
1251
+ },
1252
+ {
1253
+ "id": 39,
1254
+ "index": 10,
1255
+ "char": "'",
1256
+ "width": 8,
1257
+ "height": 15,
1258
+ "xoffset": 0,
1259
+ "yoffset": 3,
1260
+ "xadvance": 8,
1261
+ "chnl": 15,
1262
+ "x": 285,
1263
+ "y": 165,
1264
+ "page": 0
1265
+ },
1266
+ {
1267
+ "id": 45,
1268
+ "index": 16,
1269
+ "char": "-",
1270
+ "width": 15,
1271
+ "height": 8,
1272
+ "xoffset": -1,
1273
+ "yoffset": 20,
1274
+ "xadvance": 14,
1275
+ "chnl": 15,
1276
+ "x": 253,
1277
+ "y": 229,
1278
+ "page": 0
1279
+ },
1280
+ {
1281
+ "id": 44,
1282
+ "index": 15,
1283
+ "char": ",",
1284
+ "width": 8,
1285
+ "height": 14,
1286
+ "xoffset": 1,
1287
+ "yoffset": 29,
1288
+ "xadvance": 12,
1289
+ "chnl": 15,
1290
+ "x": 252,
1291
+ "y": 90,
1292
+ "page": 0
1293
+ },
1294
+ {
1295
+ "id": 96,
1296
+ "index": 67,
1297
+ "char": "`",
1298
+ "width": 12,
1299
+ "height": 10,
1300
+ "xoffset": 0,
1301
+ "yoffset": 3,
1302
+ "xadvance": 14,
1303
+ "chnl": 15,
1304
+ "x": 236,
1305
+ "y": 165,
1306
+ "page": 0
1307
+ },
1308
+ {
1309
+ "id": 46,
1310
+ "index": 17,
1311
+ "char": ".",
1312
+ "width": 8,
1313
+ "height": 8,
1314
+ "xoffset": 2,
1315
+ "yoffset": 29,
1316
+ "xadvance": 12,
1317
+ "chnl": 15,
1318
+ "x": 285,
1319
+ "y": 181,
1320
+ "page": 0
1321
+ },
1322
+ {
1323
+ "id": 32,
1324
+ "index": 3,
1325
+ "char": " ",
1326
+ "width": 0,
1327
+ "height": 0,
1328
+ "xoffset": -2,
1329
+ "yoffset": 33,
1330
+ "xadvance": 12,
1331
+ "chnl": 15,
1332
+ "x": 42,
1333
+ "y": 122,
1334
+ "page": 0
1335
+ }
1336
+ ],
1337
+ "info": {
1338
+ "face": "arial",
1339
+ "size": 42,
1340
+ "bold": 0,
1341
+ "italic": 0,
1342
+ "charset": [
1343
+ " ",
1344
+ "N",
1345
+ "T",
1346
+ "e",
1347
+ "t",
1348
+ "w",
1349
+ "x",
1350
+ "!",
1351
+ "\"",
1352
+ "#",
1353
+ "$",
1354
+ "%",
1355
+ "&",
1356
+ "'",
1357
+ "(",
1358
+ ")",
1359
+ "*",
1360
+ "+",
1361
+ ",",
1362
+ "-",
1363
+ ".",
1364
+ "/",
1365
+ "0",
1366
+ "1",
1367
+ "2",
1368
+ "3",
1369
+ "4",
1370
+ "5",
1371
+ "6",
1372
+ "7",
1373
+ "8",
1374
+ "9",
1375
+ ":",
1376
+ ";",
1377
+ "<",
1378
+ "=",
1379
+ ">",
1380
+ "?",
1381
+ "@",
1382
+ "A",
1383
+ "B",
1384
+ "C",
1385
+ "D",
1386
+ "E",
1387
+ "F",
1388
+ "G",
1389
+ "H",
1390
+ "I",
1391
+ "J",
1392
+ "K",
1393
+ "L",
1394
+ "M",
1395
+ "O",
1396
+ "P",
1397
+ "Q",
1398
+ "R",
1399
+ "S",
1400
+ "U",
1401
+ "V",
1402
+ "W",
1403
+ "X",
1404
+ "Y",
1405
+ "Z",
1406
+ "[",
1407
+ "\\",
1408
+ "]",
1409
+ "^",
1410
+ "_",
1411
+ "`",
1412
+ "a",
1413
+ "b",
1414
+ "c",
1415
+ "d",
1416
+ "f",
1417
+ "g",
1418
+ "h",
1419
+ "i",
1420
+ "j",
1421
+ "k",
1422
+ "l",
1423
+ "m",
1424
+ "n",
1425
+ "o",
1426
+ "p",
1427
+ "q",
1428
+ "r",
1429
+ "s",
1430
+ "u",
1431
+ "v",
1432
+ "y",
1433
+ "z",
1434
+ "{",
1435
+ "|",
1436
+ "}",
1437
+ "~"
1438
+ ],
1439
+ "unicode": 1,
1440
+ "stretchH": 100,
1441
+ "smooth": 1,
1442
+ "aa": 1,
1443
+ "padding": [
1444
+ 2,
1445
+ 2,
1446
+ 2,
1447
+ 2
1448
+ ],
1449
+ "spacing": [
1450
+ 0,
1451
+ 0
1452
+ ],
1453
+ "outline": 0
1454
+ },
1455
+ "common": {
1456
+ "lineHeight": 46,
1457
+ "base": 33,
1458
+ "scaleW": 293,
1459
+ "scaleH": 264,
1460
+ "pages": 1,
1461
+ "packed": 0,
1462
+ "alphaChnl": 0,
1463
+ "redChnl": 0,
1464
+ "greenChnl": 0,
1465
+ "blueChnl": 0
1466
+ },
1467
+ "distanceField": {
1468
+ "fieldType": "msdf",
1469
+ "distanceRange": 4
1470
+ },
1471
+ "kernings": []
1472
+ }
src/include/needle/arial.png ADDED
File without changes