@footgun/cobalt 0.1.1 → 0.2.0

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/esbuild.js CHANGED
@@ -10,8 +10,6 @@ esbuild.build({
10
10
  format: 'esm',
11
11
  target: 'es2022',
12
12
  plugins: [
13
- // minifying used to break the code for some reason, but I think it's fixed
14
- // so I'll re-enable this and see how it goes!
15
13
  glsl({ minify: true }),
16
14
  http()
17
15
  ],
@@ -1,2 +1,2 @@
1
1
  export { default as ECS } from 'https://cdn.skypack.dev/pin/ecs@v0.21.0-P934LlnmSF5QgmGODbyN/mode=imports/optimized/ecs.js'
2
- export { mat4, vec2, vec3, vec4 } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js'
2
+ export { mat3, mat4, vec2, vec3, vec4 } from 'https://wgpu-matrix.org/dist/3.x/wgpu-matrix.module.js'
@@ -59,133 +59,7 @@
59
59
  <canvas id="viewport" width="480" height="270"></canvas>
60
60
  </div>
61
61
 
62
- <script type="module">
63
- import Game from './Game.js'
64
- import * as Cobalt from '../../bundle.js'
65
- import constants from './constants.js'
66
- import dat from 'https://cdn.skypack.dev/pin/dat.gui@v0.7.9-2wtQAdFH5SRwnJLDWGNz/mode=imports,min/optimized/dat.gui.js'
67
- import debounce from 'https://cdn.skypack.dev/pin/lodash.debounce@v4.0.8-4GXU9B066R3Th6HmjZmO/lodash.debounce.js'
68
- //import animationSystem from './system-animation.js'
69
- import rendererSystem from './system-renderer.js'
70
- import spriteEntity from './entity-sprite.js'
71
- import { ECS, vec2 } from './deps.js'
72
-
73
-
74
- async function main () {
75
-
76
- const canvas = document.querySelector('canvas')
77
-
78
- const viewportWidth = constants.GAME_WIDTH
79
- const viewportHeight = constants.GAME_HEIGHT
80
- Game.renderer = await Cobalt.init(canvas, viewportWidth, viewportHeight)
81
-
82
- // instantiate all resource nodes
83
-
84
- const pNode = await Cobalt.initNode(Game.renderer, {
85
- type: 'cobalt:primitives',
86
- refs: {
87
- // key is the var name defined in this node
88
- // value is the var name in the cobalt resources dictionary
89
- color: 'FRAME_TEXTURE_VIEW',
90
- },
91
- options: {
92
- //zIndex: 5,
93
- }
94
- })
95
-
96
- // use resizeViewport to init values on load:
97
- resizeViewport(Game.renderer, window.innerWidth, window.innerHeight)
98
-
99
- // window resize is *expensive* - best to debounce:
100
- const debouncedResize = debounce(function () {
101
- resizeViewport(Game.renderer, window.innerWidth, window.innerHeight)
102
- }, 50)
103
-
104
- window.addEventListener('resize', debouncedResize, { passive: true })
105
-
106
- const world = ECS.createWorld()
107
- ECS.addSystem(world, rendererSystem(Game.renderer))
108
-
109
- Cobalt.setViewportPosition(Game.renderer, [ 240, 135 ])
110
-
111
- Game.world = world
112
-
113
- const gameLoop = function () {
114
- const newTime = performance.now()
115
- const frameTime = newTime - Game.lastFrameTime
116
- Game.lastFrameTime = newTime
117
- ECS.update(world, frameTime)
118
- ECS.cleanup(world)
119
-
120
- render(Game)
121
-
122
- requestAnimationFrame(gameLoop)
123
- }
124
-
125
- let box1Angle = 0
126
- let box2Angle = 0
127
-
128
- const render = function (context) {
129
- const pNode = context.renderer.nodes.find((n) => n.type === 'cobalt:primitives')
130
-
131
- pNode.clear()
132
-
133
- // start end color width
134
- pNode.line([ 10, 10 ], [ 150, 100 ], [ 1, 0, 0, 1 ], 5)
135
- pNode.line([ 100, 10 ], [ 100, 100 ], [ 1, 1, 0, 1 ], 1)
136
- pNode.line([ 450, 10 ], [ 100, 100 ], [ 0, 1, 1, 1 ], 1)
137
-
138
- pNode.filledEllipse([ 180, 80 ], 20, 40, 20, [ 1, 0, 1, 0.6 ])
139
-
140
- pNode.filledEllipse([ 192, 140 ], 8, 8, 20, [ 1, 1, 1, 0.9 ])
141
-
142
- pNode.ellipse([ 240, 140 ], 8, 18, 20, [ 0, 0, 1, 0.9 ])
143
-
144
- // pos w h c ϴ
145
- pNode.filledBox([ 50, 170 ], 42, 23, [ 0, 1, 0, 1 ], box1Angle)
146
-
147
- // pos w h c ϴ lineW
148
- pNode.box([ 120, 130 ], 42, 23, [ 1, 0, 1, 1 ], box2Angle, 1)
149
-
150
- box1Angle -= 0.05
151
- box2Angle += 0.05
152
- }
153
-
154
- requestAnimationFrame(gameLoop)
155
- }
156
-
157
-
158
- function resizeViewport (renderer, width, height) {
159
-
160
- const { canvas, device } = renderer
161
-
162
- // determine which screen dimension is most constrained
163
- // we floor the render scale to an integer because we get weird texture artifacts when trying to render at
164
- // certain float values (e.g., 3.0145833333333334)
165
- const renderScale = Math.floor(Math.min(width/constants.GAME_WIDTH, height/constants.GAME_HEIGHT))
166
-
167
- canvas.width = Math.ceil(constants.GAME_WIDTH)
168
- canvas.height = Math.ceil(constants.GAME_HEIGHT)
169
-
170
- Cobalt.setViewportDimensions(renderer, constants.GAME_WIDTH, constants.GAME_HEIGHT)
171
-
172
- // https://www.khronos.org/webgl/wiki/HandlingHighDPI
173
- // webgl display resolution size within canvas
174
- const resolution = window.devicePixelRatio || 1
175
-
176
- // center the canvas if native window doesn't match aspect ratio
177
- canvas.style.width = canvas.width * renderScale + 'px'
178
- canvas.style.height = canvas.height * renderScale + 'px'
179
-
180
- canvas.style.left = Math.round((width - canvas.width * renderScale) / 2) + 'px'
181
- canvas.style.top = Math.round((height - canvas.height * renderScale) / 2) + 'px'
182
- }
183
-
184
-
185
- main()
186
-
187
- </script>
188
-
62
+ <script type="module" src="./main.js"></script>
189
63
 
190
64
  </body>
191
65
  </html>
@@ -0,0 +1,86 @@
1
+ import Global from './Global.js'
2
+ import * as Cobalt from '../../bundle.js'
3
+ import constants from './constants.js'
4
+ import dat from 'https://cdn.skypack.dev/pin/dat.gui@v0.7.9-2wtQAdFH5SRwnJLDWGNz/mode=imports,min/optimized/dat.gui.js'
5
+ import debounce from 'https://cdn.skypack.dev/pin/lodash.debounce@v4.0.8-4GXU9B066R3Th6HmjZmO/lodash.debounce.js'
6
+ import rendererSystem from './system-renderer.js'
7
+ import { ECS, vec2 } from './deps.js'
8
+
9
+
10
+ async function main () {
11
+
12
+ const canvas = document.querySelector('canvas')
13
+
14
+ const viewportWidth = constants.GAME_WIDTH
15
+ const viewportHeight = constants.GAME_HEIGHT
16
+ Global.renderer = await Cobalt.init(canvas, viewportWidth, viewportHeight)
17
+
18
+ // instantiate all resource nodes
19
+
20
+ const pNode = await Cobalt.initNode(Global.renderer, {
21
+ type: 'cobalt:primitives',
22
+ refs: {
23
+ // key is the var name defined in this node
24
+ // value is the var name in the cobalt resources dictionary
25
+ color: 'FRAME_TEXTURE_VIEW',
26
+ },
27
+ options: { }
28
+ })
29
+
30
+ // use resizeViewport to init values on load:
31
+ resizeViewport(Global.renderer, window.innerWidth, window.innerHeight)
32
+
33
+ // window resize is *expensive* - best to debounce:
34
+ const debouncedResize = debounce(function () {
35
+ resizeViewport(Global.renderer, window.innerWidth, window.innerHeight)
36
+ }, 50)
37
+
38
+ window.addEventListener('resize', debouncedResize, { passive: true })
39
+
40
+ const world = ECS.createWorld()
41
+ ECS.addSystem(world, rendererSystem)
42
+
43
+ Global.world = world
44
+
45
+ const gameLoop = function () {
46
+ const newTime = performance.now()
47
+ const frameTime = newTime - Global.lastFrameTime
48
+ Global.lastFrameTime = newTime
49
+ ECS.update(world, frameTime)
50
+ ECS.cleanup(world)
51
+
52
+ requestAnimationFrame(gameLoop)
53
+ }
54
+
55
+ requestAnimationFrame(gameLoop)
56
+ }
57
+
58
+
59
+ function resizeViewport (renderer, width, height) {
60
+
61
+ const { canvas, device } = renderer
62
+
63
+ // determine which screen dimension is most constrained
64
+ // we floor the render scale to an integer because we get weird texture artifacts when trying to render at
65
+ // certain float values (e.g., 3.0145833333333334)
66
+ const renderScale = Math.floor(Math.min(width/constants.GAME_WIDTH, height/constants.GAME_HEIGHT))
67
+
68
+ canvas.width = Math.ceil(constants.GAME_WIDTH)
69
+ canvas.height = Math.ceil(constants.GAME_HEIGHT)
70
+
71
+ Cobalt.setViewportDimensions(renderer, constants.GAME_WIDTH, constants.GAME_HEIGHT)
72
+
73
+ // https://www.khronos.org/webgl/wiki/HandlingHighDPI
74
+ // webgl display resolution size within canvas
75
+ const resolution = window.devicePixelRatio || 1
76
+
77
+ // center the canvas if native window doesn't match aspect ratio
78
+ canvas.style.width = canvas.width * renderScale + 'px'
79
+ canvas.style.height = canvas.height * renderScale + 'px'
80
+
81
+ canvas.style.left = Math.round((width - canvas.width * renderScale) / 2) + 'px'
82
+ canvas.style.top = Math.round((height - canvas.height * renderScale) / 2) + 'px'
83
+ }
84
+
85
+
86
+ main()
@@ -1,37 +1,85 @@
1
1
  import * as Cobalt from '../../bundle.js'
2
- import Game from './Game.js'
2
+ import Global from './Global.js'
3
3
  import { ECS } from './deps.js'
4
4
 
5
5
 
6
- const SPRITE_QUERY = [ 'sprite' ]
6
+ // @param Object renderer Cobalt render state
7
+ export default function rendererSystem (world) {
8
+
9
+ // Normallly you should never store state in a system
10
+ // but this is a simple demo, so whatever.
11
+ let box1Angle = 0
12
+ let box2Angle = 0
13
+ let zoom = 1
7
14
 
8
15
 
9
- // @param Object renderer Cobalt render state
10
- export default function createRendererSystem (renderer) {
16
+ const onUpdate = function (/*dt*/) {
17
+ const renderer = Global.renderer
18
+ const device = renderer.device
19
+ const context = renderer.context
11
20
 
12
- // temporary variables, allocated once to avoid garbage collection
13
- const buf = new Float32Array(136) // tile instance data stored in a UBO
14
- const removedEntities = {
15
- count: 0,
16
- entries: new Array(100)
17
- }
21
+ const pNode = renderer.nodes.find((n) => n.type === 'cobalt:primitives')
22
+
23
+ pNode.clear()
24
+
25
+ pNode.save()
26
+ pNode.translate([ 250, 120 ])
27
+ pNode.rotate(box2Angle)
28
+ pNode.scale([ 1.5 + Math.sin(zoom), 1.5 + Math.sign(zoom) ])
29
+ pNode.translate([ -250, -120 ])
30
+ const pt1 = [ 150, 120 ]
31
+ const pt2 = [ 350, 120 ]
32
+ pNode.line(pt1, pt2, [ 1, 0, 0, 1 ], 2)
33
+ pNode.restore()
34
+
35
+ pNode.save()
36
+ pNode.translate([ 50, 170 ])
37
+ pNode.rotate(box2Angle)
38
+ pNode.translate([ -50, -170 ])
39
+ pNode.filledBox([ 50, 170 ], 42, 23, [ 0, 1, 0, 1 ])
40
+
41
+ pNode.restore()
42
+
43
+ pNode.line([ 100, 10 ], [ 100, 100 ], [ 1, 1, 0, 1 ], 1)
44
+ pNode.line([ 450, 10 ], [ 100, 100 ], [ 0, 1, 1, 1 ], 1)
18
45
 
19
- return function rendererSystem (world) {
20
- const onUpdate = function (/*dt*/) {
21
-
22
- const device = renderer.device
23
- const context = renderer.context
46
+ pNode.filledEllipse([ 180, 80 ], 20, 40, 20, [ 1, 0, 1, 0.6 ])
24
47
 
25
- ECS.getEntities(world, SPRITE_QUERY, 'removed', removedEntities)
48
+ pNode.filledEllipse([ 192, 140 ], 8, 8, 20, [ 1, 1, 1, 0.9 ])
26
49
 
27
- for (let i=0; i < removedEntities.count; i++) {
28
- const oldSprite = removedEntities.entries[i]
29
- oldSprite.spriteNode.removeSprite(oldSprite.sprite.cobaltSpriteId)
30
- }
50
+ pNode.ellipse([ 240, 140 ], 8, 18, 20, [ 0, 0, 1, 0.9 ])
31
51
 
32
- Cobalt.draw(Game.renderer)
33
- }
52
+ // pos w h c lineW
53
+ pNode.box([ 120, 130 ], 42, 23, [ 1, 0, 1, 1 ], 1)
34
54
 
35
- return { onUpdate }
55
+
56
+ const segments = [
57
+ [ [ 200, 200 ], [ 210, 230 ] ],
58
+ [ [ 210, 230 ], [ 240, 180 ] ],
59
+ [ [ 240, 180 ], [ 260, 199 ] ],
60
+
61
+ ]
62
+ pNode.strokePath(segments, [ 0, 1, 0, 1 ], 1)
63
+
64
+
65
+ pNode.translate([ 330, 170 ])
66
+ pNode.rotate(-box2Angle)
67
+ pNode.translate([ -330, -170 ])
68
+
69
+ const points = [
70
+ [ 300, 200 ],
71
+ [ 310, 220 ],
72
+ [ 370, 150 ],
73
+ [ 315, 110 ],
74
+ ]
75
+ pNode.filledPath(points, [ 0, 1, 1, 1 ])
76
+
77
+ box1Angle -= 0.05
78
+ box2Angle += 0.05
79
+ zoom += 0.05
80
+
81
+ Cobalt.draw(Global.renderer)
36
82
  }
83
+
84
+ return { onUpdate }
37
85
  }
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@footgun/cobalt",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "main": "bundle.js",
6
- "description": "An opinionated 2D node graph based on WebGpu",
6
+ "description": "A 2D WebGpu renderer",
7
7
  "homepage": "https://github.com/mreinstein/cobalt",
8
8
  "keywords": [
9
9
  "webgpu",
@@ -24,11 +24,14 @@
24
24
  },
25
25
  "devDependencies": {
26
26
  "@hyrious/esbuild-plugin-http": "^0.1.5",
27
- "@webgpu/types": "^0.1.44",
27
+ "@webgpu/types": "^0.1.51",
28
28
  "esbuild": "^0.24.0",
29
29
  "esbuild-plugin-glsl": "^1.1.0"
30
30
  },
31
31
  "dependencies": {
32
- "wgpu-matrix": "^3.0.2"
32
+ "cdt2d": "^1.0.0",
33
+ "remove-array-items": "^3.0.0",
34
+ "round-half-up-symmetric": "^2.0.0",
35
+ "wgpu-matrix": "^3.3.0"
33
36
  }
34
37
  }
@@ -1,5 +1,5 @@
1
1
  import uuid from '../uuid.js'
2
- import { vec2 } from '../deps.js'
2
+ import { vec2 } from 'wgpu-matrix'
3
3
 
4
4
 
5
5
  // public API to interact with a lighting/shadows node.
@@ -4,7 +4,7 @@ import overlayWGSL from './overlay.wgsl'
4
4
  import sortedBinaryInsert from '../sprite/sorted-binary-insert.js'
5
5
  import uuid from '../uuid.js'
6
6
  import { FLOAT32S_PER_SPRITE } from './constants.js'
7
- import { mat4, vec3, vec4 } from '../deps.js'
7
+ import { mat4, vec3, vec4 } from 'wgpu-matrix'
8
8
 
9
9
 
10
10
  // a sprite renderer with coordinates in screen space. useful for HUD/ui stuff
@@ -1,7 +1,8 @@
1
- import primitivesWGSL from './primitives.wgsl'
2
- import publicAPI from './public-api.js'
3
- import { FLOAT32S_PER_SPRITE } from './constants.js'
4
- import { round, mat4, vec2, vec3 } from '../deps.js'
1
+ import primitivesWGSL from './primitives.wgsl'
2
+ import publicAPI from './public-api.js'
3
+ import { FLOAT32S_PER_SPRITE } from './constants.js'
4
+ import round from 'round-half-up-symmetric'
5
+ import { mat4, mat3, vec2, vec3 } from 'wgpu-matrix'
5
6
 
6
7
 
7
8
  // a graphics primitives renderer (lines, boxes, etc.)
@@ -164,7 +165,12 @@ async function init (cobalt, node) {
164
165
  vertexCount: 0,
165
166
 
166
167
  dirty: false, // when more stuff has been drawn and vertexBuffer needs updating
167
- vertices, // x, y, x, y, ...
168
+ vertices, // [ x, y, x, y, ... ]
169
+
170
+ // saving/restoring will push/pop transforms off of this stack.
171
+ // works very similarly to HTML Canvas's transforms.
172
+ // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
173
+ transforms: [ mat3.identity() ],
168
174
  }
169
175
  }
170
176
 
@@ -216,6 +222,7 @@ function destroy (node) {
216
222
  node.data.vertexBuffer = null
217
223
  node.data.uniformBuffer.destroy()
218
224
  node.data.uniformBuffer = null
225
+ node.data.transforms.length = 0
219
226
  }
220
227
 
221
228
 
@@ -228,25 +235,10 @@ function _writeMatricesBuffer (cobalt, node) {
228
235
  // left right bottom top near far
229
236
  const projection = mat4.ortho(0, GAME_WIDTH, GAME_HEIGHT, 0, -10.0, 10.0)
230
237
 
231
-
232
-
233
- // TODO: if this doesn't introduce jitter into the crossroads render, remove this disabled code entirely.
234
- //
235
- // I'm disabling the rounding because I think it fails in cases where units are not expressed in pixels
236
- // e.g., most physics engines operate on meters, not pixels, so we don't want to round to the nearest integer as that
237
- // probably isn't high enough resolution. That would mean the camera could be snapped by up to 0.5 meters
238
- // in that case. I think the better solution for expressing camera position in pixels is to round before calling
239
- // cobalt.setViewportPosition(...)
240
- //
241
- // set 3d camera position
242
- //vec3.set(-round(viewport.position[0]), -round(viewport.position[1]), 0, _tmpVec3)
243
-
244
238
  vec3.set(-cobalt.viewport.position[0], -cobalt.viewport.position[1], 0, _tmpVec3)
245
239
 
246
240
  const view = mat4.translation(_tmpVec3)
247
241
 
248
-
249
242
  device.queue.writeBuffer(node.data.uniformBuffer, 0, view.buffer)
250
243
  device.queue.writeBuffer(node.data.uniformBuffer, 64, projection.buffer)
251
244
  }
252
-