blockbench/js/painter.js

902 lines
26 KiB
JavaScript
Raw Normal View History

2018-03-29 02:48:11 +08:00
class BBPainter {
constructor() {
this.color = 0x0000ffff
this.currentPixel = [-1, -1]
this.brushChanges = false
this.current = {/*texture, image*/}
2018-10-18 01:50:25 +08:00
this.background_color = new ColorPicker({
id: 'background_color',
label: true,
private: true,
})
2018-03-29 02:48:11 +08:00
}
edit(texture, cb, options) {
2018-12-16 23:18:20 +08:00
if (!options.no_undo) {
2018-10-18 01:50:25 +08:00
Undo.initEdit({textures: [texture], bitmap: true})
2018-03-29 02:48:11 +08:00
}
2018-12-27 21:03:04 +08:00
var instance = Painter.current[options.method === 'canvas' ? 'canvas' : 'image']
Painter.current[options.method === 'canvas' ? 'image' : 'canvas'] = undefined
2018-03-29 02:48:11 +08:00
if (options.use_cache &&
texture === Painter.current.texture &&
2018-12-27 21:03:04 +08:00
typeof instance === 'object'
2018-03-29 02:48:11 +08:00
) {
2018-12-27 21:03:04 +08:00
//IS CACHED
if (options.method !== 'canvas') {
instance = cb(instance) || instance
} else {
cb(instance)
}
2018-12-16 23:18:20 +08:00
if (options.no_update === true) {
return;
}
2018-12-27 21:03:04 +08:00
if (options.method !== 'canvas') {
Painter.current.image.getBase64(Jimp.MIME_PNG, function(a, dataUrl){
2018-12-03 02:37:06 +08:00
texture.updateSource(dataUrl)
2018-12-16 23:18:20 +08:00
if (!options.no_undo) {
2018-10-18 01:50:25 +08:00
Undo.finishEdit('edit_texture')
}
})
2018-12-27 21:03:04 +08:00
} else {
texture.updateSource(instance.toDataURL())
if (!options.no_undo) {
Undo.finishEdit('edit_texture')
}
}
} else {
if (options.method !== 'canvas') {
Painter.current.texture = texture
Jimp.read(Buffer.from(texture.source.replace('data:image/png;base64,', ''), 'base64')).then(function(image) {
image = cb(image) || image
Painter.current.image = image
image.getBase64(Jimp.MIME_PNG, function(a, dataUrl){
texture.updateSource(dataUrl)
if (!options.no_undo) {
Undo.finishEdit('edit_texture')
}
})
})
} else {
Painter.current.texture = texture
var c = Painter.current.canvas = document.createElement('canvas')
var ctx = c.getContext('2d');
c.width = texture.res;
c.height = texture.img.naturalHeight;
ctx.drawImage(texture.img, 0, 0)
cb(c)
texture.updateSource(c.toDataURL())
if (!options.no_undo) {
Undo.finishEdit('edit_texture')
}
}
2018-03-29 02:48:11 +08:00
}
}
2018-04-05 23:11:29 +08:00
startBrushCanvas(data, event) {
2018-10-24 03:49:04 +08:00
Painter.current.x = Painter.current.y = 0
2018-04-05 23:11:29 +08:00
var texture = getTextureById(data.cube.faces[data.face].texture)
2018-10-18 01:50:25 +08:00
if (!texture) {
Blockbench.showQuickMessage('message.untextured')
}
2018-04-05 23:11:29 +08:00
if (texture) {
var x = Math.floor( data.intersects[0].uv.x * texture.img.naturalWidth )
var y = Math.floor( (1-data.intersects[0].uv.y) * texture.img.naturalHeight )
Painter.startBrush(texture, x, y, data.cube.faces[data.face].uv, event)
}
2018-12-27 21:03:04 +08:00
if (Toolbox.selected.id !== 'color_picker' && texture) {
2018-04-05 23:11:29 +08:00
document.addEventListener('mousemove', Painter.moveBrushCanvas, false );
document.addEventListener('mouseup', Painter.stopBrushCanvas, false );
2018-03-29 02:48:11 +08:00
}
}
2018-04-05 23:11:29 +08:00
moveBrushCanvas(force) {
2018-03-29 02:48:11 +08:00
var data = Canvas.raycast()
if (data) {
var texture = getTextureById(data.cube.faces[data.face].texture)
2018-10-18 01:50:25 +08:00
if (texture) {
2018-10-24 03:49:04 +08:00
var x, y, new_face;
var end_x = x = Math.floor( data.intersects[0].uv.x * texture.img.naturalWidth )
var end_y = y = Math.floor( (1-data.intersects[0].uv.y) * texture.img.naturalHeight )
if (x === Painter.current.x && y === Painter.current.y) {
2018-12-27 21:03:04 +08:00
return
2018-10-24 03:49:04 +08:00
}
2018-11-12 04:19:08 +08:00
if (Painter.current.face !== data.face || Painter.current.cube !== data.cube) {
2018-10-24 03:49:04 +08:00
Painter.current.x = x
Painter.current.y = y
Painter.current.face = data.face
2018-11-12 04:19:08 +08:00
Painter.current.cube = data.cube
2018-10-24 03:49:04 +08:00
new_face = true
2018-12-27 21:03:04 +08:00
if (texture !== Painter.current.texture) {
Undo.current_save.addTexture(texture)
}
2018-10-24 03:49:04 +08:00
}
var diff = {
x: x - (Painter.current.x||x),
y: y - (Painter.current.y||y),
}
var length = Math.sqrt(diff.x*diff.x + diff.y*diff.y)
if (new_face && !length) {
length = 1
}
var i = 0;
while (i < length) {
x = end_x - diff.x / length * i
y = end_y - diff.y / length * i
2018-12-16 23:18:20 +08:00
Painter.useBrush(texture, x, y, data.cube.faces[data.face].uv, i < length-1)
2018-10-24 03:49:04 +08:00
i++;
}
2018-12-27 21:03:04 +08:00
2018-10-24 03:49:04 +08:00
Painter.current.x = end_x
Painter.current.y = end_y
2018-04-05 23:11:29 +08:00
}
}
}
stopBrushCanvas() {
document.removeEventListener( 'mousemove', Painter.moveBrushCanvas, false );
document.removeEventListener( 'mouseup', Painter.stopBrushCanvas, false );
Painter.stopBrush()
}
startBrush(texture, x, y, uvTag, event) {
2018-12-27 21:03:04 +08:00
if (Toolbox.selected.id !== 'color_picker') {
2018-10-18 01:50:25 +08:00
Undo.initEdit({textures: [texture], bitmap: true})
Painter.brushChanges = false
Painter.useBrush(texture, x, y, uvTag)
2018-10-24 03:49:04 +08:00
Painter.current.x = x;
Painter.current.y = y;
2018-04-05 23:11:29 +08:00
} else {
Painter.colorPicker(texture, x, y)
}
}
colorPicker(texture, x, y) {
function getPxColor(image) {
var c = image.getPixelColor(x,y)
c = tinycolor(Jimp.intToRGBA(c))
2018-10-18 01:50:25 +08:00
BarItems.brush_color.set(c)
2018-04-05 23:11:29 +08:00
}
if (texture.mode == 'bitmap') {
2018-11-12 04:19:08 +08:00
Jimp.read(Buffer.from(texture.source.replace('data:image/png;base64,', ''), 'base64')).then(getPxColor)
2018-04-05 23:11:29 +08:00
} else {
2018-11-12 04:19:08 +08:00
Jimp.read(texture.source).then(getPxColor)
2018-04-05 23:11:29 +08:00
}
}
2018-12-16 23:18:20 +08:00
useBrush(texture, x, y, uvTag, no_update) {
2018-04-05 23:11:29 +08:00
if ((Painter.currentPixel[0] !== x || Painter.currentPixel[1] !== y)) {
Painter.currentPixel = [x, y]
Painter.brushChanges = true
2018-03-29 02:48:11 +08:00
2018-12-27 21:03:04 +08:00
texture.edit(function(canvas) {
var ctx = canvas.getContext('2d')
ctx.save()
var color = BarItems.brush_color.get().toRgb();//.toRgbString()
2018-10-18 01:50:25 +08:00
var size = BarItems.slider_brush_size.get();
var softness = BarItems.slider_brush_softness.get()/100;
var b_opacity = BarItems.slider_brush_opacity.get()/100;
2018-12-27 21:03:04 +08:00
var tool = Toolbox.selected.id;
var noise = BarItems.brush_mode.get() == 'noise';
2018-03-29 02:48:11 +08:00
2018-12-27 21:03:04 +08:00
ctx.beginPath();
2018-04-05 23:11:29 +08:00
if (uvTag) {
2018-12-27 21:03:04 +08:00
var rect = Painter.editing_area = [
2018-04-05 23:11:29 +08:00
uvTag[0] / 16 * texture.img.naturalWidth,
uvTag[1] / 16 * texture.img.naturalHeight,
uvTag[2] / 16 * texture.img.naturalWidth,
uvTag[3] / 16 * texture.img.naturalHeight
]
} else {
2018-12-27 21:03:04 +08:00
var rect = Painter.editing_area = [0, 0, texture.red, texture.red]
2018-03-29 02:48:11 +08:00
}
2018-12-27 21:03:04 +08:00
for (var t = 0; t < 2; t++) {
if (rect[t] > rect[t+2]) {
[rect[t], rect[t+2]] = [rect[t+2], rect[t]]
}
2018-04-05 23:11:29 +08:00
}
2018-12-27 21:03:04 +08:00
ctx.rect(rect[0], rect[1], rect[2] - rect[0], rect[3] - rect[1])
2018-04-05 23:11:29 +08:00
2018-12-27 21:03:04 +08:00
if (tool === 'fill_tool') {
ctx.fillStyle = BarItems.brush_color.get().toRgbString()
ctx.fill()
} else {
ctx.clip()
/*ctx.beginPath();
ctx.moveTo((Painter.current.x||x)+.5, (Painter.current.y||y)+.5)
ctx.lineTo(x+.5, y+.5)
if (softness) {
ctx.filter = `blur(${ softness*size/2 }px)`;
} else {
ctx.imageSmoothingEnabled = false
}
ctx.lineWidth = size
ctx.lineCap = 'round'
if (brush_mode === 'eraser') {
ctx.globalCompositeOperation = 'destination-out'
ctx.strokeStyle = 'rgba(0,0,0,0)';
} else {
ctx.strokeStyle = color
}
ctx.stroke()*/
if (tool === 'brush_tool') {
Painter.editCircle(ctx, x, y, size, softness, function(pxcolor, opacity) {
var result_color = Painter.combineColors(pxcolor, color, opacity*b_opacity*(noise?Math.random():1));
return result_color;
})
} else if (tool === 'eraser') {
Painter.editCircle(ctx, x, y, size, softness, function(pxcolor, opacity) {
return {r: pxcolor.r, g: pxcolor.g, b: pxcolor.b, a: pxcolor.a*(1-b_opacity*opacity*(noise?Math.random():1))};
})
}
ctx.restore();
2018-04-05 23:11:29 +08:00
}
2018-12-27 21:03:04 +08:00
Painter.editing_area = undefined;
}, {method: 'canvas', no_undo: true, use_cache: true, no_update: no_update});
2018-03-29 02:48:11 +08:00
}
}
stopBrush() {
if (Painter.brushChanges) {
2018-12-27 21:03:04 +08:00
Undo.finishEdit('paint');
Painter.brushChanges = false;
2018-03-29 02:48:11 +08:00
}
2018-12-27 21:03:04 +08:00
Painter.currentPixel = [-1, -1];
2018-03-29 02:48:11 +08:00
}
combineColors(base, added, opacity) {
if (typeof base === 'number') base = Jimp.intToRGBA(base)
if (typeof added === 'number') added = Jimp.intToRGBA(added)
var original_a = added.a
added.a = (added.a)*opacity
var mix = {};
mix.a = limitNumber(1 - (1 - added.a) * (1 - base.a), 0, 1); // alpha
mix.r = Math.round((added.r * added.a / mix.a) + (base.r * base.a * (1 - added.a) / mix.a)); // red
mix.g = Math.round((added.g * added.a / mix.a) + (base.g * base.a * (1 - added.a) / mix.a)); // green
mix.b = Math.round((added.b * added.a / mix.a) + (base.b * base.a * (1 - added.a) / mix.a)); // blue
added.a = original_a
return mix;
}
2018-12-27 21:03:04 +08:00
scanCanvas(ctx, x, y, w, h, cb) {
var arr = ctx.getImageData(x, y, w, h)
for (var i = 0; i < arr.data.length; i += 4) {
var pixel = arr.data.slice(i, i+4)
var px = (i/4) % w
var py = Math.floor((i/4) / w)
pixel = cb(x+px, y+py, pixel)||pixel
pixel.forEach((p, pi) => {
arr.data[i+pi] = p
})
}
ctx.putImageData(arr, x, y)
}
2018-03-29 02:48:11 +08:00
drawRectangle(image, color, rect) {
var color = Jimp.intToRGBA(color)
image.scan(rect.x, rect.y, rect.w, rect.h, function (x, y, idx) {
2018-10-18 01:50:25 +08:00
this.bitmap.data[idx + 0] = color.r
this.bitmap.data[idx + 1] = color.g
this.bitmap.data[idx + 2] = color.b
this.bitmap.data[idx + 3] = color.a
2018-03-29 02:48:11 +08:00
});
}
editFace(image, x, y, editPx) {
var x = Math.floor(Painter.editing_area[0]-0.5)
var y = Math.floor(Painter.editing_area[1]-0.5)
var width = Math.floor(Painter.editing_area[2]+1.5) - Math.floor(Painter.editing_area[0])
var height = Math.floor(Painter.editing_area[3]+1.5) - Math.floor(Painter.editing_area[1])
image.scan(x, y, width, height, function (px, py, idx) {
if (px >= this.bitmap.width ||
px < 0 ||
py >= this.bitmap.height ||
py < 0
) {
return;
}
if (
typeof Painter.editing_area === 'object' &&
(
px+0.2 < Painter.editing_area[0] ||
py+0.2 < Painter.editing_area[1] ||
px+0.2 >= Painter.editing_area[2] ||
py+0.2 >= Painter.editing_area[3]
)
) {
return;
}
var result_color = editPx({
r:this.bitmap.data[idx+0],
g:this.bitmap.data[idx+1],
b:this.bitmap.data[idx+2],
a:this.bitmap.data[idx+3]/255
})
2018-10-18 01:50:25 +08:00
this.bitmap.data[idx+0] = result_color.r
this.bitmap.data[idx+1] = result_color.g
this.bitmap.data[idx+2] = result_color.b
this.bitmap.data[idx+3] = result_color.a*255
2018-03-29 02:48:11 +08:00
});
}
2018-12-27 21:03:04 +08:00
editCircle(ctx, x, y, r, s, editPx) {
2018-03-29 02:48:11 +08:00
r = Math.round(r)
2018-12-27 21:03:04 +08:00
Painter.scanCanvas(ctx, x-r-1, y-r-1, 2*r+3, 2*r+3, function (px, py, pixel) {
2018-12-03 02:37:06 +08:00
2018-12-27 21:03:04 +08:00
if (px >= ctx.canvas.width ||
2018-03-29 02:48:11 +08:00
px < 0 ||
2018-12-27 21:03:04 +08:00
py >= ctx.canvas.height ||
2018-03-29 02:48:11 +08:00
py < 0
) {
return;
}
if (
settings.paint_side_restrict.value &&
Painter.editing_area &&
typeof Painter.editing_area === 'object' &&
(
2018-12-03 02:37:06 +08:00
px+0.02 < Math.floor(Painter.editing_area[0]) ||
py+0.02 < Math.floor(Painter.editing_area[1]) ||
px+0.02 >= Painter.editing_area[2] ||
2018-12-27 21:03:04 +08:00
py+0.02 >= Painter.editing_area[3]
2018-03-29 02:48:11 +08:00
)
) {
return;
}
px -= x;
py -= y;
var distance = Math.sqrt(px*px + py*py)
if (s*r != 0) {
var pos_on_gradient = (distance-(1-s)*r) / (s*r)
} else {
var pos_on_gradient = Math.floor(distance/r)
}
var opacity = limitNumber(1-pos_on_gradient, 0, 1)
if (opacity > 0) {
var result_color = editPx({
2018-12-27 21:03:04 +08:00
r: pixel[0],
g: pixel[1],
b: pixel[2],
a: pixel[3]/255
2018-03-29 02:48:11 +08:00
}, opacity)
2018-12-27 21:03:04 +08:00
pixel[0] = result_color.r
pixel[1] = result_color.g
pixel[2] = result_color.b
pixel[3] = result_color.a*255
2018-03-29 02:48:11 +08:00
}
});
}
2018-10-18 01:50:25 +08:00
drawRotatedRectangle(image, color, rect, cx, cy, angle) {
var color = Jimp.intToRGBA(color)
var sin = Math.sin(-Math.degToRad(angle))
var cos = Math.cos(-Math.degToRad(angle))
function rotatePoint(px, py) {
px -= cx
py -= cy
return {
x: (px * cos - py * sin) + cx,
y: (px * sin + py * cos) + cy
}
}
image.scan(0, 0, 48, 48, function (px, py, idx) {
var rotated = rotatePoint(px, py)
if (
rotated.x > rect.x-1 && rotated.x < rect.x + rect.w+2 &&
rotated.y > rect.y-1 && rotated.y < rect.y + rect.h+2
) {
var opacity = limitNumber(rect.x - rotated.x, 0, 1) +
limitNumber(rotated.x - (rect.x + rect.w), 0, 1) +
limitNumber(rect.y - rotated.y, 0, 1) +
limitNumber(rotated.y - (rect.y + rect.h), 0, 1)
opacity = 1-limitNumber(opacity*1.61, 0, 1)
if (this.bitmap.data[idx + 3]) {
opacity = 1
}
this.bitmap.data[idx + 0] = color.r
this.bitmap.data[idx + 1] = color.g
this.bitmap.data[idx + 2] = color.b
this.bitmap.data[idx + 3] = color.a*opacity
}
})
}
2018-03-29 02:48:11 +08:00
addBitmapDialog() {
var lines = []
2018-10-18 01:50:25 +08:00
lines.push({label: 'dialog.create_texture.name', node: '<input class="dark_bordered half" type="text" id="bitmap_name">'})
lines.push({label: 'dialog.create_texture.folder', node: '<input class="dark_bordered half" type="text" id="bitmap_folder">'})
if (elements.length > 0) {
lines.push({label: 'dialog.create_texture.template', node: '<input type="checkbox" id="bitmap_doTemplate">'})
2018-03-29 02:48:11 +08:00
}
2018-10-18 01:50:25 +08:00
lines.push({widget: Painter.background_color})
lines.push({label: 'dialog.create_texture.resolution', node: '<input class="dark_bordered" style="width:72px" type="number" id="bitmap_resolution">'})
2018-03-29 02:48:11 +08:00
var dialog = new Dialog({
id: 'add_bitmap',
2018-10-18 01:50:25 +08:00
title: tl('dialog.create_texture.title'),
2018-03-29 02:48:11 +08:00
draggable: true,
lines: lines,
onConfirm: function() {
Painter.addBitmapFromDialog()
dialog.hide()
}
})
dialog.show()
$('.dialog#add_bitmap input#bitmap_doTemplate').click(function() {
2018-10-18 01:50:25 +08:00
if (Painter.background_color.get().toHex8() === 'ffffffff') {
Painter.background_color.set('#00000000')
2018-03-29 02:48:11 +08:00
}
})
}
testSetup() {
Painter.addBitmap()
main_uv.setFace('up')
addCube().extend({to:[16,1,16]})
elements[0].faces.up.uv = [0,0,16,16]
textures[0].apply()
Canvas.updateSelected()
updateSelection()
}
addBitmapFromDialog() {
2018-10-18 09:32:42 +08:00
var color = Painter.background_color.get()
2018-03-29 02:48:11 +08:00
Painter.addBitmap({
2018-12-03 02:37:06 +08:00
res: limitNumber(parseInt($('.dialog#add_bitmap input#bitmap_resolution').val()), 16, 2048),
2018-03-29 02:48:11 +08:00
color: color,
name: $('.dialog#add_bitmap input#bitmap_name').val(),
folder: $('.dialog#add_bitmap input#bitmap_folder').val(),
particle: 'auto',
entity_template: $('.dialog#add_bitmap input#bitmap_doTemplate').is(':checked')
})
}
addBitmap(options, after) {
if (typeof options !== 'object') {
options = {}
}
if (isNaN(options.res) || !options.res) {
options.res = 16
}
if (options.color === undefined) {
2018-10-18 09:32:42 +08:00
options.color = new tinycolor().toRgb()
2018-03-29 02:48:11 +08:00
}
2018-12-27 21:03:04 +08:00
if (Blockbench.entity_mode) {
options.texture = textures[0]
}
2018-03-29 02:48:11 +08:00
var texture = new Texture({
mode: 'bitmap',
2018-04-05 23:11:29 +08:00
keep_size: true,
2018-03-29 02:48:11 +08:00
res: options.res,
name: options.name ? options.name : 'texture',
folder: options.folder ? options.folder : 'blocks'
}).add()
function makeTexture(dataUrl) {
texture.fromDataURL(dataUrl)
switch (options.particle) {
case 'auto':
texture.fillParticle();
break;
case true:
texture.enableParticle();
break;
}
if (typeof after === 'function') {
after(texture)
}
2018-10-18 01:50:25 +08:00
return texture;
2018-03-29 02:48:11 +08:00
}
if (options.entity_template === true) {
2018-12-27 21:03:04 +08:00
Undo.initEdit({textures: [], cubes: Blockbench.entity_mode ? elements : selected, uv_only: true})
Painter.generateTemplate(options.res, options.color, makeTexture, options.texture)
Undo.finishEdit({textures: [texture], cubes: Blockbench.entity_mode ? elements : selected, uv_only: true})
2018-03-29 02:48:11 +08:00
} else {
2018-12-27 21:03:04 +08:00
Undo.initEdit({textures: []})
2018-03-29 02:48:11 +08:00
Painter.generateBlank(options.res, options.res, options.color, makeTexture)
2018-12-27 21:03:04 +08:00
Undo.finishEdit({textures: [texture]})
2018-03-29 02:48:11 +08:00
}
}
generateBlank(height, width, color, cb) {
2018-12-27 21:03:04 +08:00
var canvas = document.createElement('canvas')
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d')
ctx.fillStyle = new tinycolor(color).toRgbString()
ctx.fillRect(0, 0, width, height)
cb(canvas.toDataURL())
2018-03-29 02:48:11 +08:00
}
2018-12-27 21:03:04 +08:00
generateTemplate(res, background_color, cb, texture) {
2018-03-29 02:48:11 +08:00
function cubeTempl(obj) {
2018-12-03 02:37:06 +08:00
var min = Blockbench.entity_mode ? 0 : 1
this.x = obj.size(0, true) || min
this.y = obj.size(1, true) || min
this.z = obj.size(2, true) || min
2018-03-29 02:48:11 +08:00
this.obj = obj
this.height = this.z + this.y
this.width = 2* (this.x + this.z)
return this;
}
2018-12-03 02:37:06 +08:00
var res_multiple = res / 16
2018-12-27 21:03:04 +08:00
var templates = []
2018-03-29 02:48:11 +08:00
var max_x_pos = 0
var line_y_pos = 0;
var valid_cubes = 0;
var lines = [[]]
2018-10-18 01:50:25 +08:00
var line_length = Math.sqrt(elements.length/2)
2018-03-29 02:48:11 +08:00
var o = 0
2018-11-12 04:19:08 +08:00
var cubes = Blockbench.entity_mode ? elements.slice() : selected.slice()
2018-10-18 01:50:25 +08:00
var avg_size = 0;
var i = cubes.length-1
while (i >= 0) {
let obj = cubes[i]
if (obj.visibility === false) {
cubes.splice(i,1)
} else {
obj.template_size = (obj.size(2, true) + obj.size(1, true)) + (obj.size(2, true) + obj.size(0, true))*2
avg_size += obj.template_size
}
i--;
}
avg_size /= cubes.length
cubes.sort(function(a,b) {
return b.template_size - a.template_size
})
i = 0
var ox = 0
2018-12-27 21:03:04 +08:00
cubes.forEach(function(obj) {
2018-10-18 01:50:25 +08:00
if (ox >= line_length) {
2018-03-29 02:48:11 +08:00
o = 0
2018-10-18 01:50:25 +08:00
ox = 0
2018-03-29 02:48:11 +08:00
i++
lines[i] = []
}
lines[i][o] = obj
2018-10-18 01:50:25 +08:00
o++;
ox += obj.template_size/avg_size
2018-03-29 02:48:11 +08:00
})
lines.forEach(function(b) {
//Data
var temps = []
b.forEach(function(s, si) {
if (s.type === 'cube') {
temps.push(new cubeTempl(s))
valid_cubes++;
}
})
//Defaults
//Find the maximum height of the line
var max_height = 0
temps.forEach(function(t) {
max_height = Math.max(max_height, t.height)
})
var x_pos = 0
var y_pos = 0 //Y Position of current area relative to this bone
var filled_x_pos = 0;
//Algorithm
temps.forEach(function(t) {
if (y_pos > 0 && (y_pos + t.height) <= max_height) {
//same column
t.posx = x_pos
t.posy = y_pos + line_y_pos
filled_x_pos = Math.max(filled_x_pos, x_pos+t.width)
y_pos += t.height
} else {
//new column
x_pos = filled_x_pos
y_pos = t.height
t.posx = x_pos
t.posy = line_y_pos
filled_x_pos = Math.max(filled_x_pos, x_pos+t.width)
}
//size of widest bone
max_x_pos = Math.max(max_x_pos, filled_x_pos)
2018-12-27 21:03:04 +08:00
templates.push(t)
2018-03-29 02:48:11 +08:00
})
line_y_pos += max_height
})
//Cancel if no cubes
if (valid_cubes == 0) {
Blockbench.showMessage('No valid cubes', 'center')
return;
}
2018-10-18 01:50:25 +08:00
function getNextPower(num, min) {
var i = min ? min : 2
while (i < num && i < 4000) {
i *= 2
}
return i;
2018-03-29 02:48:11 +08:00
}
//Size
var max_size = Math.max(max_x_pos, line_y_pos)
2018-10-18 01:50:25 +08:00
max_size = Math.ceil(max_size/16)*16//getNextPower(max_size, 16)
2018-10-18 09:32:42 +08:00
if (background_color.getAlpha() != 0) {
background_color = background_color.toInteger()
}
2018-12-27 21:03:04 +08:00
var canvas = document.createElement('canvas')
canvas.width = canvas.height = max_size*res_multiple;
var ctx = canvas.getContext('2d')
ctx.imageSmoothingEnabled = false;
2018-03-29 02:48:11 +08:00
2018-12-27 21:03:04 +08:00
function drawTemplateRectangle(border_color, color, coords) {
2018-10-18 09:32:42 +08:00
if (typeof background_color === 'number') {
2018-10-18 01:50:25 +08:00
border_color = background_color
color = undefined
}
2018-12-27 21:03:04 +08:00
ctx.fillStyle = border_color
ctx.fillRect(
coords.x*res_multiple,
coords.y*res_multiple,
coords.w*res_multiple,
coords.h*res_multiple
)
2018-10-18 01:50:25 +08:00
if (coords.w <= 2 || coords.h <= 2 || !color) return;
2018-12-27 21:03:04 +08:00
ctx.fillStyle = color
ctx.fillRect(
coords.x * res_multiple + 1,
coords.y * res_multiple + 1,
coords.w * res_multiple - 2,
coords.h * res_multiple - 2
)
}
function drawTexture(face, coords) {
if (!Blockbench.entity_mode) {
if (face.texture === undefined || face.texture === null) return;
texture = getTextureById(face.texture)
}
if (!texture || !texture.img) return;
var uv = face.uv;
var src = getRectangle(uv[0], uv[1], uv[2], uv[3])
ctx.drawImage(
texture.img,
src.ax/16 * texture.img.naturalWidth,
src.ay/16 * texture.img.naturalHeight,
src.x /16 * texture.img.naturalWidth,
src.y /16 * texture.img.naturalHeight,
coords.x*res_multiple,
coords.y*res_multiple,
coords.w*res_multiple,
coords.h*res_multiple
)
2018-03-29 02:48:11 +08:00
}
2018-10-18 01:50:25 +08:00
2018-12-16 23:18:20 +08:00
var face_data = {
2018-12-27 21:03:04 +08:00
up: {c1: '#b4d4e1', c2: '#ecf8fd', place: t => {return {x: t.posx+t.z, y: t.posy, w: t.x, h: t.z}}},
down: {c1: '#536174', c2: '#6e788c', place: t => {return {x: t.posx+t.z+t.x, y: t.posy, w: t.x, h: t.z}}},
east: {c1: '#43e88d', c2: '#7BFFA3', place: t => {return {x: t.posx, y: t.posy+t.z, w: t.z, h: t.y}}},
north: {c1: '#5bbcf4', c2: '#7BD4FF', place: t => {return {x: t.posx+t.z, y: t.posy+t.z, w: t.x, h: t.y}}},
west: {c1: '#f48686', c2: '#FFA7A4', place: t => {return {x: t.posx+t.z+t.x, y: t.posy+t.z, w: t.z, h: t.y}}},
south: {c1: '#f8dd72', c2: '#FFF899', place: t => {return {x: t.posx+t.z+t.x+t.z,y: t.posy+t.z, w: t.x, h: t.y}}},
2018-12-16 23:18:20 +08:00
}
2018-03-29 02:48:11 +08:00
//Drawing
2018-12-27 21:03:04 +08:00
templates.forEach(function(t) {
for (var face in face_data) {
let d = face_data[face]
if (options.use_texture || !t.obj.faces[face].texture) {
//Colored
drawTemplateRectangle(d.c1, d.c2, d.place(t))
} else {
//Texture
drawTexture(t.obj.faces[face], d.place(t))
}
}
let obj = t.obj
obj.uv_offset[0] = t.posx
obj.uv_offset[1] = t.posy
if (!Blockbench.entity_mode) {
var size = obj.size(undefined, true)
var face_list = [
{face: 'north', fIndex: 10, from: [size[2], size[2]], size: [size[0], size[1]]},
{face: 'east', fIndex: 0, from: [0, size[2]], size: [size[2], size[1]]},
{face: 'south', fIndex: 8, from: [size[2]*2 + size[0], size[2]], size: [size[0], size[1]]},
{face: 'west', fIndex: 2, from: [size[2] + size[0], size[2]], size: [size[2], size[1]]},
{face: 'up', fIndex: 4, from: [size[2]+size[0], size[2]], size: [-size[0], -size[2]]},
{face: 'down', fIndex: 6, from: [size[2]+size[0]*2, 0], size: [-size[0], size[2]]}
]
face_list.forEach(function(f) {
obj.faces[f.face].uv[0] = (f.from[0] + Math.floor(obj.uv_offset[0]+0.0000001)) / max_size * 16,
obj.faces[f.face].uv[1] = (f.from[1] + Math.floor(obj.uv_offset[1]+0.0000001)) / max_size * 16,
obj.faces[f.face].uv[2] = (f.from[0] + f.size[0] + Math.floor(obj.uv_offset[0]+0.0000001)) / max_size * 16,
obj.faces[f.face].uv[3] = (f.from[1] + f.size[1] + Math.floor(obj.uv_offset[1]+0.0000001)) / max_size * 16
2018-10-18 01:50:25 +08:00
2018-03-29 02:48:11 +08:00
})
2018-12-27 21:03:04 +08:00
}
2018-03-29 02:48:11 +08:00
})
2018-12-27 21:03:04 +08:00
var dataUrl = canvas.toDataURL()
var texture = cb(dataUrl)
entityMode.setResolution(max_size, max_size, true)
if (texture && !Blockbench.entity_mode) {
templates.forEach(function(t) {
t.obj.applyTexture(texture, true)
t.obj.autouv = 0
})
}
2018-03-29 02:48:11 +08:00
}
}
var Painter = new BBPainter()
2018-11-12 04:19:08 +08:00
BARS.defineActions(function() {
2018-12-03 02:37:06 +08:00
new Tool({
id: 'brush_tool',
icon: 'fa-paint-brush',
category: 'tools',
toolbar: 'brush',
2018-12-27 21:03:04 +08:00
alt_tool: 'color_picker',
2018-12-03 02:37:06 +08:00
selectFace: true,
transformerMode: 'hidden',
paintTool: true,
allowWireframe: false,
keybind: new Keybind({key: 66}),
2018-12-27 21:03:04 +08:00
condition: () => Modes.id === 'paint',
onCanvasClick: function(data) {
Painter.startBrushCanvas(data, data.event)
},
onSelect: function() {
BarItems.slider_brush_size.update()
BarItems.slider_brush_softness.update()
BarItems.slider_brush_opacity.update()
$('.UVEditor').find('#uv_size').hide()
},
onUnselect: function() {
$('.UVEditor').find('#uv_size').show()
}
})
new Tool({
id: 'fill_tool',
icon: 'format_color_fill',
category: 'tools',
toolbar: 'brush',
alt_tool: 'color_picker',
selectFace: true,
transformerMode: 'hidden',
paintTool: true,
allowWireframe: false,
condition: () => Modes.id === 'paint',
onCanvasClick: function(data) {
Painter.startBrushCanvas(data, data.event)
},
onSelect: function() {
BarItems.slider_brush_size.update()
BarItems.slider_brush_softness.update()
BarItems.slider_brush_opacity.update()
$('.UVEditor').find('#uv_size').hide()
},
onUnselect: function() {
$('.UVEditor').find('#uv_size').show()
}
})
new Tool({
id: 'eraser',
icon: 'fa-eraser',
category: 'tools',
toolbar: 'brush',
selectFace: true,
transformerMode: 'hidden',
paintTool: true,
allowWireframe: false,
condition: () => Modes.id === 'paint',
onCanvasClick: function(data) {
Painter.startBrushCanvas(data, data.event)
},
onSelect: function() {
BarItems.slider_brush_size.update()
BarItems.slider_brush_softness.update()
BarItems.slider_brush_opacity.update()
$('.UVEditor').find('#uv_size').hide()
},
onUnselect: function() {
$('.UVEditor').find('#uv_size').show()
}
})
new Tool({
id: 'color_picker',
icon: 'colorize',
category: 'tools',
toolbar: 'brush',
selectFace: true,
transformerMode: 'hidden',
paintTool: true,
allowWireframe: false,
condition: () => Modes.id === 'paint',
2018-12-03 02:37:06 +08:00
onCanvasClick: function(data) {
Painter.startBrushCanvas(data, data.event)
},
onSelect: function() {
BarItems.slider_brush_size.update()
BarItems.slider_brush_softness.update()
BarItems.slider_brush_opacity.update()
$('.UVEditor').find('#uv_size').hide()
},
onUnselect: function() {
$('.UVEditor').find('#uv_size').show()
}
})
2018-11-12 04:19:08 +08:00
new ColorPicker({
id: 'brush_color',
2018-12-27 21:03:04 +08:00
condition: () => (Toolbox && ['brush_tool', 'color_picker', 'fill_tool'].includes(Toolbox.selected.id)),
2018-11-12 04:19:08 +08:00
palette: true
})
new BarSelect({
id: 'brush_mode',
2018-12-27 21:03:04 +08:00
condition: () => Toolbox && (Toolbox.selected.id === 'brush_tool' || Toolbox.selected.id === 'eraser'),
2018-11-12 04:19:08 +08:00
options: {
brush: true,
2018-12-27 21:03:04 +08:00
noise: true
2018-11-12 04:19:08 +08:00
}
})
new NumSlider({
id: 'slider_brush_size',
2018-12-27 21:03:04 +08:00
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id)),
2018-11-12 04:19:08 +08:00
settings: {
min: 1, max: 20, step: 1, default: 1,
}
})
new NumSlider({
id: 'slider_brush_softness',
2018-12-27 21:03:04 +08:00
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id)),
2018-11-12 04:19:08 +08:00
settings: {
min: 0, max: 100, default: 0,
interval: function(event) {
if (event.shiftKey && event.ctrlKey) {
return 0.25;
} else if (event.shiftKey) {
return 5;
} else if (event.ctrlKey) {
return 1;
} else {
return 10;
}
}
}
})
new NumSlider({
id: 'slider_brush_opacity',
2018-12-27 21:03:04 +08:00
condition: () => (Toolbox && ['brush_tool', 'eraser'].includes(Toolbox.selected.id)),
2018-11-12 04:19:08 +08:00
settings: {
min: 0, max: 100, default: 100,
interval: function(event) {
if (event.shiftKey && event.ctrlKey) {
return 0.25;
} else if (event.shiftKey) {
return 5;
} else if (event.ctrlKey) {
return 1;
} else {
return 10;
}
}
}
})
})