Initial commit
This commit is contained in:
commit
d2f693ccf5
14
.babelrc
Normal file
14
.babelrc
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [
|
||||
[
|
||||
"@babel/plugin-transform-runtime",
|
||||
{
|
||||
"corejs": false,
|
||||
"helpers": true,
|
||||
"regenerator": true,
|
||||
"useESModules": false
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
[*.js]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
/package-lock.json
|
||||
/**/dna.txt
|
||||
*.db
|
||||
*.sqlite3
|
10
index.html
Normal file
10
index.html
Normal file
@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<style type="text/css">*{margin:0;padding:0;}body{width:100%;height:100%;}body,html{overflow:hidden;}</style>
|
||||
<title>bechunked</title>
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
</html>
|
31
package.json
Normal file
31
package.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"name": "bechunked",
|
||||
"version": "0.0.0",
|
||||
"description": "testing canvas stuff",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"serve": "node ./serve.js",
|
||||
"build": "webpack -p",
|
||||
"watch": "webpack -w --mode=development"
|
||||
},
|
||||
"keywords": [],
|
||||
"private": true,
|
||||
"author": "Evert \"Diamond\" Prants <evert@lunasqu.ee>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.1.6",
|
||||
"@babel/plugin-transform-runtime": "^7.1.0",
|
||||
"@babel/preset-env": "^7.1.6",
|
||||
"babel-loader": "^8.0.4",
|
||||
"copy-webpack-plugin": "^5.0.4",
|
||||
"express": "^4.16.4",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"standard": "^12.0.1",
|
||||
"webpack": "^4.26.0",
|
||||
"webpack-command": "^0.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.1.5",
|
||||
"simplex-noise": "^2.4.0"
|
||||
}
|
||||
}
|
11
serve.js
Executable file
11
serve.js
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
const express = require('express')
|
||||
const path = require('path')
|
||||
const app = express()
|
||||
|
||||
app.set('env', 'development')
|
||||
app.use('/', express.static(path.join(__dirname, 'dist')))
|
||||
|
||||
app.listen(3000, function () {
|
||||
console.log('server listening on 0.0.0.0:3000')
|
||||
})
|
63
src/image.js
Normal file
63
src/image.js
Normal file
@ -0,0 +1,63 @@
|
||||
import Resource from './resource'
|
||||
|
||||
let imageCache = {}
|
||||
|
||||
class Image {
|
||||
constructor (file, img) {
|
||||
this.file = file
|
||||
this.img = img
|
||||
}
|
||||
|
||||
static async load (file) {
|
||||
if (imageCache[file]) return imageCache[file]
|
||||
let img = await Resource.loadImage(file)
|
||||
let imgCl = new Image(file, img)
|
||||
imageCache[file] = imgCl
|
||||
return imgCl
|
||||
}
|
||||
|
||||
get width () {
|
||||
return this.img.width
|
||||
}
|
||||
|
||||
get height () {
|
||||
return this.img.height
|
||||
}
|
||||
|
||||
draw (ctx, x, y, w, h) {
|
||||
ctx.drawImage(this.img, x, y, w, h)
|
||||
}
|
||||
}
|
||||
|
||||
class TileMap extends Image {
|
||||
constructor (file, img, xaspect, yaspect) {
|
||||
super(file, img)
|
||||
this.xaspect = xaspect
|
||||
this.yaspect = yaspect
|
||||
this.xcount = this.width / xaspect
|
||||
this.ycount = this.height / yaspect
|
||||
this.tiles = []
|
||||
|
||||
for (let y = 0; y < this.ycount; y++) {
|
||||
for (let x = 0; x < this.xcount; x++) {
|
||||
this.tiles.push({ x, y })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw (ctx, index, x, y, w, h) {
|
||||
let box = this.tiles[index]
|
||||
if (!box) return
|
||||
ctx.drawImage(this.img, box.x * this.xaspect, box.y * this.yaspect, this.xaspect, this.yaspect, x, y, w, h)
|
||||
}
|
||||
|
||||
static async load (file, x, y) {
|
||||
if (imageCache[file]) return imageCache[file]
|
||||
let img = await Resource.loadImage(file, true)
|
||||
let imgCl = new TileMap(file, img, x, y)
|
||||
imageCache[file] = imgCl
|
||||
return imgCl
|
||||
}
|
||||
}
|
||||
|
||||
export { Image, TileMap }
|
271
src/index.js
Normal file
271
src/index.js
Normal file
@ -0,0 +1,271 @@
|
||||
/* global requestAnimationFrame */
|
||||
import Simplex from 'simplex-noise'
|
||||
import Input from './input'
|
||||
|
||||
class BetterNoise extends Simplex {
|
||||
// amplitude - Controls the amount the height changes. The higher, the taller the hills.
|
||||
// persistence - Controls details, value in [0,1]. Higher increases grain, lower increases smoothness.
|
||||
// octaves - Number of noise layers
|
||||
// period - Distance above which we start to see similarities. The higher, the longer "hills" will be on a terrain.
|
||||
// lacunarity - Controls period change across octaves. 2 is usually a good value to address all detail levels.
|
||||
constructor (seed, amplitude = 15, persistence = 0.4, octaves = 5, period = 80, lacunarity = 2) {
|
||||
super(seed)
|
||||
this.seed = seed
|
||||
|
||||
this.amplitude = amplitude
|
||||
this.period = period
|
||||
this.lacunarity = lacunarity
|
||||
this.octaves = octaves
|
||||
this.persistence = persistence
|
||||
}
|
||||
|
||||
getNoise (zx, zy) {
|
||||
let x = zx / this.period
|
||||
let y = zy / this.period
|
||||
|
||||
let amp = 1.0
|
||||
let max = 1.0
|
||||
let sum = this.noise2D(x, y)
|
||||
|
||||
let i = 0
|
||||
while (++i < this.octaves) {
|
||||
x *= this.lacunarity
|
||||
y *= this.lacunarity
|
||||
amp *= this.persistence
|
||||
max += amp
|
||||
sum += this.noise2D(x, y) * amp
|
||||
}
|
||||
|
||||
return (sum / max) * this.amplitude
|
||||
}
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
document.body.appendChild(canvas)
|
||||
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
|
||||
const ChunkSize = 16
|
||||
const TileSize = 32
|
||||
const TotalSize = ChunkSize * TileSize
|
||||
|
||||
class Tile {
|
||||
constructor (name, color = '#000', image, size = TileSize) {
|
||||
this.name = name
|
||||
this.size = size
|
||||
this.color = color
|
||||
this.image = image
|
||||
}
|
||||
|
||||
// Render tile at precise render coordinates on the canvas, no relativity
|
||||
draw (r, x, y, s = 1) {
|
||||
let scaledSize = this.size / s
|
||||
if (this.image) {
|
||||
if (this.image.tiles && this.index != null) {
|
||||
return this.image.draw(r, this.index, x, y, scaledSize, scaledSize)
|
||||
}
|
||||
|
||||
if (this.image.src) {
|
||||
return r.drawImage(this.image, x, y, scaledSize, scaledSize)
|
||||
}
|
||||
|
||||
return this.image.draw(r, x, y, scaledSize, scaledSize)
|
||||
}
|
||||
|
||||
r.fillStyle = this.color
|
||||
r.fillRect(x, y, scaledSize, scaledSize)
|
||||
}
|
||||
}
|
||||
|
||||
class Chunk {
|
||||
constructor (x, y) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
this.tiles = {}
|
||||
}
|
||||
|
||||
getTile (x, y) {
|
||||
if (x > ChunkSize || y > ChunkSize || x < 0 || y < 0) throw new Error('Out Of Bounds!')
|
||||
return this.getTilei(Chunk.ptoi(x, y))
|
||||
}
|
||||
|
||||
getTilei (i) {
|
||||
if (i < 0 || i > ChunkSize * ChunkSize) return null
|
||||
return this.tiles[i]
|
||||
}
|
||||
|
||||
setTile (x, y, j) {
|
||||
if (x > ChunkSize || y > ChunkSize || x < 0 || y < 0) throw new Error('Out Of Bounds!')
|
||||
this.setTilei(Chunk.ptoi(x, y), j)
|
||||
}
|
||||
|
||||
setTilei (i, j) {
|
||||
if (i < 0 || i > ChunkSize * ChunkSize) throw new Error('Out Of Bounds!')
|
||||
this.tiles[i] = j
|
||||
}
|
||||
|
||||
// Index -> 2D Position
|
||||
static itop (i) {
|
||||
return { x: Math.floor(i % ChunkSize), y: Math.floor(i / ChunkSize) }
|
||||
}
|
||||
|
||||
// 2D Position -> Index
|
||||
static ptoi (x, y) {
|
||||
return x + y * ChunkSize
|
||||
}
|
||||
|
||||
// @r : canvas context
|
||||
// @c : chunk
|
||||
// @o : offset
|
||||
// @s : scale
|
||||
static render (r, c, o = { x: 0, y: 0 }, s = 1) {
|
||||
if (!c) return null
|
||||
for (let i in c.tiles) {
|
||||
let tile = c.tiles[i]
|
||||
if (tile == null || !(tile instanceof Tile)) continue
|
||||
let pos = Chunk.itop(i)
|
||||
tile.draw(r,
|
||||
Math.floor(((pos.x * TileSize / s) + (c.x * TotalSize / s)) + o.x),
|
||||
Math.floor(((pos.y * TileSize / s) + (c.y * TotalSize / s)) + o.y),
|
||||
s
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChunkWorld {
|
||||
constructor () {
|
||||
this.chunks = []
|
||||
}
|
||||
|
||||
static generateTerrain (c) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
for (let j = 0; j < 16; j++) {
|
||||
let h = noise.getNoise(i + (c.x * ChunkSize), j + (c.y * ChunkSize))
|
||||
let tile = 0
|
||||
if (h > noise.amplitude * 0.5) {
|
||||
tile = 3
|
||||
} else if (h > noise.amplitude * 0.3) {
|
||||
tile = 2
|
||||
} else if (h > noise.amplitude * 0.2) {
|
||||
tile = 1
|
||||
}
|
||||
c.setTile(i, j, tiles[tile])
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
static getPositionsInViewport (pos, scale) {
|
||||
// Calculate how many chunks could theoretically fit in this viewport
|
||||
let swidth = Math.ceil(canvas.width / (TotalSize / scale)) + 1
|
||||
let sheight = Math.ceil(canvas.height / (TotalSize / scale)) + 1
|
||||
let positions = []
|
||||
|
||||
// Loop through the theoretical positions
|
||||
for (let px = 0; px < swidth; px++) {
|
||||
for (let py = 0; py < sheight; py++) {
|
||||
let cx = (px * (TotalSize / scale)) - pos.x
|
||||
let cy = (py * (TotalSize / scale)) - pos.y
|
||||
|
||||
positions.push({
|
||||
x: Math.floor(cx / (TotalSize / scale)),
|
||||
y: Math.floor(cy / (TotalSize / scale))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return positions
|
||||
}
|
||||
|
||||
update (pos, scale) {
|
||||
let positions = ChunkWorld.getPositionsInViewport(pos, scale)
|
||||
for (let p in positions) {
|
||||
let cpos = positions[p]
|
||||
let found = false
|
||||
for (let i in this.chunks) {
|
||||
let chunk = this.chunks[i]
|
||||
if (chunk.x == cpos.x && chunk.y == cpos.y) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
let chunk = new Chunk(cpos.x, cpos.y)
|
||||
ChunkWorld.generateTerrain(chunk)
|
||||
this.chunks.push(chunk)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render (r, o, s) {
|
||||
for (let i in this.chunks) {
|
||||
let chunk = this.chunks[i]
|
||||
let scaledSize = TotalSize / s
|
||||
// Convert to chunk-space coordinates
|
||||
let localX = Math.floor(cursor.x / scaledSize) * -1
|
||||
let localY = Math.floor(cursor.y / scaledSize) * -1
|
||||
let aw = Math.ceil(canvas.width / scaledSize) + 1
|
||||
let ah = Math.ceil(canvas.height / scaledSize)
|
||||
|
||||
if (chunk.x <= localX + aw && chunk.x + 1 >= localX &&
|
||||
chunk.y <= localY + ah && chunk.y + 1 >= localY) {
|
||||
Chunk.render(r, chunk, o, s)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let world = new ChunkWorld()
|
||||
let input = new Input(canvas)
|
||||
let noise = new BetterNoise(1, /*amplitude = */15, /*persistence = */0.4, /*octaves = */5, /*period = */120, /*lacunarity = */2)
|
||||
let zoom = 2
|
||||
|
||||
let tiles = [
|
||||
new Tile('water', '#028fdb'),
|
||||
new Tile('sand', '#cec673'),
|
||||
new Tile('grass', '#007c1f'),
|
||||
new Tile('stone', '#515151')
|
||||
]
|
||||
|
||||
let cursor = {x: 0, y: 0}
|
||||
|
||||
function render () {
|
||||
ctx.fillStyle = '#00aaff'
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||
world.render(ctx, cursor, zoom)
|
||||
}
|
||||
|
||||
function update (dt) {
|
||||
if (input.isDown('w')) {
|
||||
cursor.y += 6
|
||||
} else if (input.isDown('s')) {
|
||||
cursor.y -= 6
|
||||
}
|
||||
|
||||
if (input.isDown('a')) {
|
||||
cursor.x += 6
|
||||
} else if (input.isDown('d')) {
|
||||
cursor.x -= 6
|
||||
}
|
||||
|
||||
world.update(cursor, zoom)
|
||||
input.update()
|
||||
}
|
||||
|
||||
function gameLoop () {
|
||||
requestAnimationFrame(gameLoop)
|
||||
update()
|
||||
render()
|
||||
}
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
})
|
||||
|
||||
gameLoop()
|
222
src/input.js
Normal file
222
src/input.js
Normal file
@ -0,0 +1,222 @@
|
||||
|
||||
const specialKeyMap = {
|
||||
'backspace': 8,
|
||||
'tab': 9,
|
||||
'enter': 13,
|
||||
'shift': 16,
|
||||
'ctrl': 17,
|
||||
'alt': 18,
|
||||
'pausebreak': 19,
|
||||
'capslock': 20,
|
||||
'escape': 27,
|
||||
'pgup': 33,
|
||||
'pgdown': 34,
|
||||
'end': 35,
|
||||
'home': 36,
|
||||
'left': 37,
|
||||
'up': 38,
|
||||
'right': 39,
|
||||
'down': 40,
|
||||
'insert': 45,
|
||||
'delete': 46,
|
||||
'left-window': 91,
|
||||
'right-window': 92,
|
||||
'select': 93,
|
||||
'numpad0': 96,
|
||||
'numpad1': 97,
|
||||
'numpad2': 98,
|
||||
'numpad3': 99,
|
||||
'numpad4': 100,
|
||||
'numpad5': 101,
|
||||
'numpad6': 102,
|
||||
'numpad7': 103,
|
||||
'numpad8': 104,
|
||||
'numpad9': 105,
|
||||
'multiply': 106,
|
||||
'add': 107,
|
||||
'subtract': 109,
|
||||
'decimal': 110,
|
||||
'divide': 111,
|
||||
'f1': 112,
|
||||
'f2': 113,
|
||||
'f3': 114,
|
||||
'f4': 115,
|
||||
'f5': 116,
|
||||
'f6': 117,
|
||||
'f7': 118,
|
||||
'f8': 119,
|
||||
'f9': 120,
|
||||
'f10': 121,
|
||||
'f11': 122,
|
||||
'f12': 123,
|
||||
'numlock': 144,
|
||||
'scrolllock': 145,
|
||||
'semi-colon': 186,
|
||||
'equals': 187,
|
||||
'comma': 188,
|
||||
'dash': 189,
|
||||
'period': 190,
|
||||
'fwdslash': 191,
|
||||
'grave': 192,
|
||||
'open-bracket': 219,
|
||||
'bkslash': 220,
|
||||
'close-braket': 221,
|
||||
'single-quote': 222
|
||||
}
|
||||
|
||||
class Input {
|
||||
constructor (canvas) {
|
||||
this.keyList = {}
|
||||
this.previousKeyList = {}
|
||||
this.canvas = canvas
|
||||
|
||||
this.mouse = {
|
||||
pos: { x: 0, y: 0 },
|
||||
frame: { x: 0, y: 0 },
|
||||
previous: { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (e) => this.keyDown(e), false)
|
||||
window.addEventListener('keyup', (e) => this.keyUp(e), false)
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
let x
|
||||
let y
|
||||
|
||||
if (e.changedTouches) {
|
||||
let touch = e.changedTouches[0]
|
||||
if (touch) {
|
||||
e.pageX = touch.pageX
|
||||
e.pageY = touch.pageY
|
||||
}
|
||||
}
|
||||
|
||||
if (e.pageX || e.pageY) {
|
||||
x = e.pageX
|
||||
y = e.pageY
|
||||
} else {
|
||||
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft
|
||||
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||
}
|
||||
|
||||
x -= canvas.offsetLeft
|
||||
y -= canvas.offsetTop
|
||||
|
||||
this.mouse.frame.x = x
|
||||
this.mouse.frame.y = y
|
||||
}, false)
|
||||
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault()
|
||||
this.mouse['btn' + e.button] = true
|
||||
})
|
||||
|
||||
canvas.addEventListener('mouseup', (e) => {
|
||||
e.preventDefault()
|
||||
this.mouse['btn' + e.button] = false
|
||||
})
|
||||
|
||||
canvas.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault()
|
||||
})
|
||||
}
|
||||
|
||||
get mousePos () {
|
||||
return this.mouse.pos
|
||||
}
|
||||
|
||||
get mouseMoved () {
|
||||
return (this.mouse.pos.x !== this.mouse.previous.x ||
|
||||
this.mouse.pos.y !== this.mouse.previous.y)
|
||||
}
|
||||
|
||||
get mouseOffset () {
|
||||
return {
|
||||
x: this.mouse.previous.x - this.mouse.pos.x,
|
||||
y: this.mouse.previous.y - this.mouse.pos.y
|
||||
}
|
||||
}
|
||||
|
||||
toggleKey (keyCode, on) {
|
||||
// Find key in special key list
|
||||
let key = null
|
||||
for (let k in specialKeyMap) {
|
||||
let val = specialKeyMap[k]
|
||||
if (keyCode === val) {
|
||||
key = k
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Use fromCharCode
|
||||
if (!key) {
|
||||
key = String.fromCharCode(keyCode).toLowerCase()
|
||||
}
|
||||
|
||||
this.keyList[key] = (on === true)
|
||||
}
|
||||
|
||||
keyDown (e) {
|
||||
let keycode
|
||||
|
||||
if (window.event) {
|
||||
keycode = window.event.keyCode
|
||||
} else if (e) {
|
||||
keycode = e.which
|
||||
}
|
||||
|
||||
this.toggleKey(keycode, true)
|
||||
}
|
||||
|
||||
keyUp (e) {
|
||||
let keycode
|
||||
|
||||
if (window.event) {
|
||||
keycode = window.event.keyCode
|
||||
} else if (e) {
|
||||
keycode = e.which
|
||||
}
|
||||
|
||||
this.toggleKey(keycode, false)
|
||||
}
|
||||
|
||||
down (key) {
|
||||
return this.keyList[key] != null ? this.keyList[key] : false
|
||||
}
|
||||
|
||||
downLast (key) {
|
||||
return this.previousKeyList[key] != null ? this.previousKeyList[key] : false
|
||||
}
|
||||
|
||||
isDown (key) {
|
||||
return this.down(key) && this.downLast(key)
|
||||
}
|
||||
|
||||
isUp (key) {
|
||||
return !this.isDown(key)
|
||||
}
|
||||
|
||||
isPressed (key) {
|
||||
return this.down(key) === true && this.downLast(key) === false
|
||||
}
|
||||
|
||||
update () {
|
||||
this.previousKeyList = {}
|
||||
for (let k in this.keyList) {
|
||||
if (this.keyList[k] === true) {
|
||||
this.previousKeyList[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Mouse positions in the previous frame
|
||||
this.mouse.previous.x = this.mouse.pos.x
|
||||
this.mouse.previous.y = this.mouse.pos.y
|
||||
|
||||
// Mouse positions in the current frame
|
||||
// Convert to OpenGL coordinate system
|
||||
this.mouse.pos.x = this.mouse.frame.x / this.canvas.width * 2 - 1
|
||||
this.mouse.pos.y = this.mouse.frame.y / this.canvas.height * 2 - 1
|
||||
}
|
||||
}
|
||||
|
||||
export default Input
|
101
src/resource.js
Normal file
101
src/resource.js
Normal file
@ -0,0 +1,101 @@
|
||||
/* global XMLHttpRequest, Image */
|
||||
|
||||
let imgCache = {}
|
||||
|
||||
function powerOfTwo (n) {
|
||||
return n && (n & (n - 1)) === 0
|
||||
}
|
||||
|
||||
function GET (url, istext) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var xmlHttp = new XMLHttpRequest()
|
||||
|
||||
xmlHttp.onreadystatechange = function () {
|
||||
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
|
||||
resolve(xmlHttp.responseText)
|
||||
} else if (xmlHttp.readyState === 4 && xmlHttp.status >= 400) {
|
||||
let err = new Error(xmlHttp.status)
|
||||
err.request = xmlHttp
|
||||
reject(err)
|
||||
}
|
||||
}
|
||||
|
||||
xmlHttp.open('GET', url, true)
|
||||
istext && (xmlHttp.responseType = 'text')
|
||||
xmlHttp.send(null)
|
||||
})
|
||||
}
|
||||
|
||||
function smartGET (data) {
|
||||
if (typeof data === 'string') {
|
||||
data = {
|
||||
url: data,
|
||||
type: 'text'
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.type) data.type = 'text'
|
||||
let istext = (data.type !== 'image' && data.type !== 'file')
|
||||
|
||||
let url = data.url
|
||||
if (!url) throw new Error('URL is required!')
|
||||
|
||||
if (data.type === 'json') {
|
||||
return new Promise((resolve, reject) => {
|
||||
GET(url).then((dtext) => {
|
||||
try {
|
||||
let jsonp = JSON.parse(dtext)
|
||||
return resolve(jsonp)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
}, reject)
|
||||
})
|
||||
}
|
||||
|
||||
return GET(data.url, istext)
|
||||
}
|
||||
|
||||
function loadImage (url, nowarn = false) {
|
||||
url = '/assets/images/' + url
|
||||
|
||||
// Ensure we don't load a texture multiple times
|
||||
if (imgCache[url]) return new Promise((resolve, reject) => resolve(imgCache[url]))
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let img = new Image()
|
||||
|
||||
img.onload = function () {
|
||||
// Friendly warnings
|
||||
if ((!powerOfTwo(img.width) || !powerOfTwo(img.height)) && !nowarn) {
|
||||
console.warn(`warn: image ${url} does not have dimensions that are powers of two. 16x16 to 128x128 recommended.`)
|
||||
}
|
||||
|
||||
if ((img.width / img.height) !== 1 && !nowarn) {
|
||||
console.warn(`warn: image ${url} does not have an aspect ratio of 1 to 1.`)
|
||||
}
|
||||
|
||||
imgCache[url] = img
|
||||
resolve(img)
|
||||
}
|
||||
|
||||
img.onerror = function (e) {
|
||||
reject(e)
|
||||
}
|
||||
|
||||
img.src = url
|
||||
})
|
||||
}
|
||||
|
||||
function imageToSampler (img) {
|
||||
let canvas = document.createElement('canvas')
|
||||
let ctx = canvas.getContext('2d')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
ctx.drawImage(img, 0, 0, img.width, img.height)
|
||||
return function (x, y) {
|
||||
return ctx.getImageData(x, y, 1, 1).data
|
||||
}
|
||||
}
|
||||
|
||||
export default { GET: smartGET, imageToSampler, loadImage }
|
32
webpack.config.js
Normal file
32
webpack.config.js
Normal file
@ -0,0 +1,32 @@
|
||||
const path = require('path')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin')
|
||||
|
||||
module.exports = (env) => {
|
||||
return {
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'app.js'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['@babel/preset-env']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
devtool: env.mode === 'development' ? 'inline-source-map' : '',
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({ template: 'index.html' }),
|
||||
new CopyWebpackPlugin([ { from: 'static' } ])
|
||||
]
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user