Initial Code
parent
1461564a14
commit
8cc20cef39
|
@ -0,0 +1,50 @@
|
|||
# Minecraft to Minetest Skin Converter
|
||||
|
||||
This converter supports creating Minetest skins. If you believe that using the upper half of the Minecraft skin is suffient this converter is likely not for you.
|
||||
|
||||
The cape, the right or left arm or leg Minecraft body parts may be copied individually.
|
||||
Also the jacket, right or left sleeves and trousers may be copied with alpha transparency to the Minetest skin.
|
||||
|
||||
It renders the skin as an animated 3d character.
|
||||
|
||||
It supports standard and HD skins.
|
||||
|
||||
Usage
|
||||
-------
|
||||
|
||||
The converters runs in the browser, after download no internet connection is requierd.
|
||||
First upload a square Minecraft skin from your local drive.
|
||||
If you have a standard skin you may want to zoom in to see some details, otherwise ignore the zoom buttons.
|
||||
Play with the "Update ..." buttons to get a first impression of the skin. You need to update again after copying data. There is no live update for the 3d character.
|
||||
Choose the background color you like or adjust it later.
|
||||
Start copying body and cloth parts. Left to some buttons is a "0.5" field. This is the alpha/transparency used while copying.
|
||||
To copy the jacket, the left sleeve or trouser set the alpha/transparency to 1.
|
||||
There is no "Undo" button, you have to copy another body part to 'undo' things.
|
||||
|
||||
Use the "Update ..." buttons again to verify the result.
|
||||
Right-click on the new skin (upper left image) and select "Save Image As ...".
|
||||
|
||||
The screenshot shows a modified vonDoomCraft HD skin.
|
||||
![N|Solid](https://github.com/godly/minetest-skin-converter/screenshot.png)
|
||||
|
||||
Typical skin locations
|
||||
-------
|
||||
|
||||
%AppData%\.minecraft\resourcepacks\entity\steve.png
|
||||
%ProgramFiles%\minetest\games\minetest_game\mods\default\models\character.png
|
||||
|
||||
Supported Browsers
|
||||
-------
|
||||
|
||||
Firefox, Chrome, Edge
|
||||
|
||||
Known issues
|
||||
-------
|
||||
|
||||
Edge blurs the images while zooming in.
|
||||
Internet Explorer (4-11) does not work.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
GPLv3
|
|
@ -0,0 +1,421 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Minetest Skin Converter</title>
|
||||
<link rel="stylesheet" type="text/css" href="character.css">
|
||||
<style type="text/css">
|
||||
/* Do not blur zoomed images; https://drafts.csswg.org/css-images-3/#the-image-rendering */
|
||||
img, div {
|
||||
image-rendering: pixelated;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: -o-crisp-edges;
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
}
|
||||
/* Hide tools and controls */
|
||||
div#imgTools {
|
||||
display: none;
|
||||
}
|
||||
div#imgControl {
|
||||
display: none;
|
||||
}
|
||||
table, td, tr {
|
||||
margin: 0em;
|
||||
padding: 0em;
|
||||
border: 0em;
|
||||
background-color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
align: left;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
td {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body id="body">
|
||||
<!-- use <u>...</u> to mark on-mouse-over help / title tag -->
|
||||
<div id="loader"><table><tr><td>
|
||||
Load a <span title="64×64 / 256×256 / 512×512 / 1024×1024 PNG file"><u>Minecraft (1:1)</u></span> or <span title="64×32 / 512×256 / 1024×512 PNG file"><u>Minetest (2:1)</u></span> skin: <input type="file" id="imgLoader" accept="image/png" onChange="loadImage()" />
|
||||
</td></tr></table></div>
|
||||
<div id="imgTools"><table><tr><td>
|
||||
Zoom to <button type="button" onclick="zoom.to('0.25')">1:4</button>
|
||||
<button type="button" onclick="zoom.to('0.5')">1:2</button>
|
||||
<button type="button" onclick="zoom.to('1')">1:1</button>
|
||||
<button type="button" onclick="zoom.to('2')">2:1</button>
|
||||
<button type="button" onclick="zoom.to('4')">4:1</button>
|
||||
<button type="button" onclick="zoom.to('8')">8:1</button>
|
||||
<button type="button" onclick="zoom.to('16')">16:1</button>
|
||||
</td><td>
|
||||
Update <button type="button" onclick="renderAs('mcCharacter')" title="Render as Minecraft Character"><u>Minecraft</u></button>
|
||||
<button type="button" onclick="renderAs('mcCloth')" title="Render as Minecraft Character with Clothes"><u>with Clothes</u></button> /
|
||||
<button type="button" onclick="renderAs('mtCharacter')" title="Render as Minetest"><u>Minetest</u></button>
|
||||
<button type="button" onclick="renderAs('mtCloth')" title="Render as Minetest Character with Clothes"><u>with Hat & Cape</u></button> 3d character.
|
||||
</td></tr><tr><td>Background Color:<input type="color" value="#FFFFFF" onchange="setBackgroundColor('dstImg', this.value)" onclick="setBackgroundColor('dstImg', this.value)"/>
|
||||
<!-- issue: onchange() is not triggered if the bg color was checkerboard before
|
||||
fix: onclick() but this changes the color value even if the user selects cancel -->
|
||||
<button type="button" onclick="setBackgroundColor('dstImg', 'checkerboard')">Checkerboard</button>
|
||||
/ <input type="color" value="#FFFFFF" onchange="setBackgroundColor('srcImg', this.value)" onclick="setBackgroundColor('srcImg', this.value)"/>
|
||||
<button type="button" onclick="setBackgroundColor('srcImg', 'checkerboard')">Checkerboard</button>
|
||||
</td><td><input type="color" value="#FFFFFF" onchange="setBackgroundColor('characterElementId', this.value)" onclick="setBackgroundColor('characterElementId', this.value)"/>
|
||||
<button type="button" onclick="setBackgroundColor('characterElementId', 'checkerboard')">Checkerboard</button>
|
||||
</td></tr></table></div>
|
||||
|
||||
<div id="imgControl"><table><tr><td>
|
||||
<button type="button" onclick="copy('areaJacket.back', 'areaCapeMinetest')" title="Copy Minecraft 'Cape' to Minetest"><u>Copy</u></button>/
|
||||
<button type="button" onclick="copy('areaCapeMinetest', 'areaNull')" title="Replace 'Cape' area with transparency"><u>Erase</u></button> Cape
|
||||
</td><td><!-- empty --></td></tr><tr><td>
|
||||
Copy<input id="alphaJacket" type="text" size="1" value="0.5" title="Alpha value"><button type="button" onclick="copy('areaJacket.all', 'areaBody.all', '0', 'alphaJacket')" title="Copy Minecraft 'Jacket' over Minetest 'Body' (including cape) with alpha transparency"><u>Jacket</u></button>/
|
||||
<button type="button" onclick="copy('areaBody.back', 'areaBody.back')" title="Restore the back of the Minetest body"><u>Body.Back</u></button>/
|
||||
<button type="button" onclick="copy('areaBody.all', 'areaBody.all')" title="Restore the whole Minetest body"><u>Body</u></button>
|
||||
</td><td><!-- empty --></td></tr><tr><td>
|
||||
Copy Left<input id="alphaTrouserLeft" type="text" size="1" value="0.5" title="Alpha value"><button type="button" onclick="copy('areaTrouserLeft.all', 'areaLegRight.all', '1', 'alphaTrouserLeft' )" title="Copy and flip 'Left Trouser' over 'Right Leg' with alpha transparency"><u>Trouser</u></button>/
|
||||
<button type="button" onclick="copy('areaLegLeft.all', 'areaLegRight.all', '1')" title="Copy and flip the left leg"><u>Leg</u></button>.
|
||||
Copy Right<input id="alphaTrouserRight" type="text" size="1" value="0.5" title="Alpha value"><button type="button" onclick="copy('areaTrouserRight.all', 'areaLegRight.all', '0', 'alphaTrouserRight' )" title="Copy 'Right Trouser' over 'Right Leg' with alpha transparency"><u>Trouser</u></button>/
|
||||
<button type="button" onclick="copy('areaLegRight.all', 'areaLegRight.all')" title="Restore the right leg"><u>Leg</u></button>
|
||||
</td><td><!-- empty --></td></tr><tr><td>
|
||||
Copy Left<input id="alphaSleeveLeft" type="text" size="1" value="0.5" title="Alpha value"><button type="button" onclick="copy('areaSleeveLeft.all', 'areaArmRight.all', '1', 'alphaSleeveLeft' )" title="Copy and flip 'Left Sleeve' over 'Right Arm' with alpha transparency"><u>Sleeve</u></button>/
|
||||
<button type="button" onclick="copy('areaArmLeft.all', 'areaArmRight.all', '1')" title="Copy and flip the left arm"><u>Arm</u></button>
|
||||
Copy Right<input id="alphaSleeveRight" type="text" size="1" value="0.5" title="Alpha value"><button type="button" onclick="copy('areaSleeveRight.all', 'areaArmRight.all', '0', 'alphaSleeveRight')" title="Copy 'Right Sleeve' over 'Right Arm' with alpha transparency"><u>Sleeve</u></button>
|
||||
<button type="button" onclick="copy('areaArmRight.all', 'areaArmRight.all')" title="Restore the right arm"><u>Arm</u></button>
|
||||
</td><td><!-- empty --></td></tr></table></div>
|
||||
|
||||
<div id="images"><table><tr><td>
|
||||
<img id="dstImg" src="" border="1" alt="Placeholder for new skin. When it shows the proper skin: Right-Click; Save Image As ..." title="Right-Click; Save Image As ..."/>
|
||||
</td><td rowspan="2" valign="top" style="min-width:64px;"><div id="characterElementId"></div></td></tr><tr><td>
|
||||
<img id="srcImg" src="" border="1" alt="Placeholder for the source skin"/><br/>
|
||||
</td></tr></table></div>
|
||||
<!-- loading local js files with UTF-8 characters will fail without charset="utf-8" -->
|
||||
<script type="text/javascript" charset="utf-8" src="zoom.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="uvareas.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="three.min.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="uvcharacter.js"></script>
|
||||
<script type="text/javascript" charset="utf-8">//<![CDATA[
|
||||
|
||||
function setBackgroundColor(dstElem, value) {
|
||||
console.log("INFO\tsetBackgroundColor(" + dstElem + ", " + value + ")");
|
||||
if (value == "checkerboard") {
|
||||
document.getElementById(dstElem).style.backgroundImage = "url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAQAAADYv8WvAAAAEklEQVQI12Oc+f8sA9NZBmMGABasA2lR+US6AAAAAElFTkSuQmCC')";
|
||||
document.getElementById(dstElem).style.backgroundSize = "16px 16px";
|
||||
} else {
|
||||
document.getElementById(dstElem).style.backgroundImage = "";
|
||||
document.getElementById(dstElem).style.backgroundColor = value;
|
||||
};
|
||||
};
|
||||
|
||||
// Strings used in HTML as IDs and in code
|
||||
// Use these vars while coding to avoid typos.
|
||||
const constImgLoader = "imgLoader";
|
||||
const constSkinMinecraft = "minecraft";
|
||||
const constSkinMinetest = "minetest";
|
||||
const constIdSrcImage = "srcImg";
|
||||
const constIdDstImage = "dstImg";
|
||||
const constLoader = "loader";
|
||||
const constImageTools = "imgTools";
|
||||
const constImageControls = "imgControl";
|
||||
const idImages = "images"
|
||||
const strTransparency = "transparency";
|
||||
var zoom = new Zooming('images');
|
||||
|
||||
// uvFaceWidth: Block size in pixels, 1/16 of the image width. 4px for standard skins with 64px width.
|
||||
var uvFaceWidth = 4;
|
||||
|
||||
// srcImg, dstImg: The public source and destination images
|
||||
var srcImg = document.getElementById(constIdSrcImage);
|
||||
var dstImg = document.getElementById(constIdDstImage);
|
||||
|
||||
// dstCanvas: The internal image buffer for the modifications (copy, alpha, mirror, ...)
|
||||
var dstCanvas = document.createElement('canvas');
|
||||
|
||||
|
||||
// Main function to load the image after the user selected it
|
||||
function loadImage() {
|
||||
// reset zoom level before loading. Otherwise "em" scaling causes random issues
|
||||
zoom.to(1);
|
||||
|
||||
// load selected image
|
||||
var file = document.getElementById(constImgLoader).files[0];
|
||||
srcImg.style.width = "auto";
|
||||
srcImg.style.height = "auto";
|
||||
srcImg.addEventListener("load", function () {
|
||||
imageLoaded();
|
||||
}, false);
|
||||
srcImg.src = window.URL.createObjectURL(file);
|
||||
// function continues in imageLoaded() as soon as the image is loaded
|
||||
}
|
||||
function imageLoaded() {
|
||||
/*
|
||||
localCharacterCanvas.width = srcImg.width;
|
||||
localCharacterCanvas.height = srcImg.width; // canvas will be should be square
|
||||
localCharacterCanvas.getContext("2d").drawImage(srcImg, 0, 0);
|
||||
minecraftCharacter.render;
|
||||
*/
|
||||
// calculate width and height of target image
|
||||
var srcWidth = srcImg.width;
|
||||
var srcHeight = srcImg.height; // Skins must be square or ½ square box. Otherwise skin conversion fails
|
||||
var dstWidth = srcWidth;
|
||||
var dstHeight = srcWidth/2; // srcWidth/2 to get ½ square. We don't rely on srcHeight
|
||||
uvFaceWidth = srcWidth / 16;
|
||||
uvFaceWidth = srcWidth / constUvWidth;
|
||||
|
||||
// assume Minecraft skin
|
||||
var srcSkinType = constSkinMinecraft;
|
||||
if (srcWidth == srcHeight*2) {
|
||||
// setting skin source to Minetest and the image format to square (widht=height).
|
||||
srcSkinType = constSkinMinetest;
|
||||
dstHeight = srcWidth;
|
||||
}
|
||||
|
||||
// Set image size to 'em' to allow zooming
|
||||
srcImg.style.width = srcWidth/16 + "em";
|
||||
srcImg.style.height = srcHeight/16 + "em";
|
||||
dstImg.style.width = dstWidth/16 + "em";
|
||||
dstImg.style.height = dstHeight/16 + "em";
|
||||
|
||||
if ( (srcWidth == srcHeight) || (srcWidth == (srcHeight*2)) ) {
|
||||
// Expected image size
|
||||
setBackgroundColor(constIdSrcImage, 'checkerboard');
|
||||
setBackgroundColor(constIdDstImage, 'checkerboard');
|
||||
// Hide loader and show zoom and background buttons
|
||||
document.getElementById(constLoader).style.display = "none";
|
||||
document.getElementById(constImageTools).style.display = "initial";
|
||||
if (srcWidth == srcHeight) {
|
||||
// Minecraft image, show copy buttons etc.
|
||||
document.getElementById(constImageControls).style.display = "initial";
|
||||
}
|
||||
} else {
|
||||
// Expect the unexpected
|
||||
alert("Please upload a 64×64 / 256×256 / 512×512 / 1024×1024 Minecraft or a 64×32 / 512×256 / 1024×512 Minetest image! (not " + srcWidth + "×" + srcHeight + ")");
|
||||
return;
|
||||
};
|
||||
|
||||
// Copy upper part of skin (1:1)
|
||||
dstCanvas.width = dstWidth;
|
||||
dstCanvas.height = dstHeight;
|
||||
var context = dstCanvas.getContext('2d');
|
||||
draw(srcImg, areaMinetest, context, areaMinetest, uvFaceWidth, 0);
|
||||
|
||||
// adjust for Minetest / Minecraft
|
||||
if (srcSkinType == constSkinMinecraft) {
|
||||
// cleanup unused areas
|
||||
draw(srcImg, areaHead.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaHead.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaHat.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaHat.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaLegRight.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaLegRight.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaBody.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaBody.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaArmRight.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaArmRight.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaUnusedMinetest, context, areaNull, uvFaceWidth, 0);
|
||||
// copy Jacket.back(Cape) to areaCapeMinetest
|
||||
draw(srcImg, areaJacket.back, context, areaCapeMinetest, uvFaceWidth, 0);
|
||||
} else {
|
||||
// srcSkinType == constSkinMinetest
|
||||
// cleanup unused areas in upper part of the image
|
||||
draw(srcImg, areaHead.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaHead.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaHat.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaHat.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaLegRight.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaLegRight.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaBody.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaBody.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaArmRight.unusedL, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaArmRight.unusedR, context, areaNull, uvFaceWidth, 0);
|
||||
draw(srcImg, areaUnusedMinecraft, context, areaNull, uvFaceWidth, 0);
|
||||
// Lower part of the image is blank, nothing to clear
|
||||
|
||||
// copy Jacket.back
|
||||
draw(srcImg, areaCapeMinetest, context, areaJacket.back, uvFaceWidth, 0);
|
||||
|
||||
// copy and mirror leg: top
|
||||
draw(srcImg, areaLegRight.top, context, areaLegLeft.top, uvFaceWidth, 1);
|
||||
// copy and mirror leg: bottom
|
||||
draw(srcImg, areaLegRight.bottom, context, areaLegLeft.bottom, uvFaceWidth, 1);
|
||||
// copy and mirror leg: right, front, left
|
||||
draw(srcImg, areaLegRight.lfr, context, areaLegLeft.lfr, uvFaceWidth, 1);
|
||||
// copy and mirror leg: back
|
||||
draw(srcImg, areaLegRight.back, context, areaLegLeft.back, uvFaceWidth, 1);
|
||||
|
||||
// copy and mirror arm: top
|
||||
draw(srcImg, areaArmRight.top, context, areaArmLeft.top, uvFaceWidth, 1);
|
||||
// copy and mirror arm: bottom
|
||||
draw(srcImg, areaArmRight.bottom, context, areaArmLeft.bottom, uvFaceWidth, 1);
|
||||
// copy and mirror arm: right, front, left
|
||||
draw(srcImg, areaArmRight.lfr, context, areaArmLeft.lfr, uvFaceWidth, 1);
|
||||
// copy and mirror arm: back
|
||||
draw(srcImg, areaArmRight.back, context, areaArmLeft.back, uvFaceWidth, 1);
|
||||
}
|
||||
|
||||
// copy data from the internal dstCanvas to the public dstImg
|
||||
var tmpDataUrl = dstCanvas.toDataURL('image/png');
|
||||
dstImg.src = tmpDataUrl;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
copy(from, to, flip, alphaId)
|
||||
from: srcArea
|
||||
to: dstArea. Set to 'areaNull' to clean the region defined in srcArea
|
||||
flip values: 0 = normal copy, 1 = flip area
|
||||
alphaId: element which stores the desired alpha value
|
||||
*/
|
||||
function copy(from, to, flip, alphaId) {
|
||||
var flipValue = 0;
|
||||
if (flip != null) {
|
||||
flipValue = flip;
|
||||
}
|
||||
var alpha = 1;
|
||||
if (alphaId) {
|
||||
alpha = document.getElementById(alphaId).value;
|
||||
};
|
||||
console.log("INFO\tcopy from " + from + " to " + to + ". Alpha: " + alpha + " (" + alphaId + "). Flip:" + flip);
|
||||
var context = dstCanvas.getContext('2d');
|
||||
areaFrom = eval(from);
|
||||
areaTo = eval(to);
|
||||
|
||||
if (areaTo == areaNull) {
|
||||
draw(srcImg, areaFrom, context, areaNull, uvFaceWidth, flipValue);
|
||||
} else if (flip == 1) {
|
||||
var fromArray = from.split(".");
|
||||
var toArray = to.split(".");
|
||||
if ( (fromArray[1] == "all") && (toArray[1] == "all") ) {
|
||||
// copy and mirror arm: top; bottom; right, front, left; back
|
||||
context.globalAlpha = alpha;
|
||||
draw(srcImg, eval(fromArray[0] + ".top"), context, eval(toArray[0] + ".top"), uvFaceWidth, flipValue);
|
||||
draw(srcImg, eval(fromArray[0] + ".bottom"), context, eval(toArray[0] + ".bottom"), uvFaceWidth, flipValue);
|
||||
draw(srcImg, eval(fromArray[0] + ".lfr"), context, eval(toArray[0] + ".lfr"), uvFaceWidth, flipValue);
|
||||
draw(srcImg, eval(fromArray[0] + ".back"), context, eval(toArray[0] + ".back"), uvFaceWidth, flipValue);
|
||||
} else {
|
||||
console.error("ERROR\tcopy 'from' and 'to' must end with '.all'");
|
||||
}
|
||||
} else {
|
||||
context.globalAlpha = alpha;
|
||||
draw(srcImg, areaFrom, context, areaTo, uvFaceWidth, flipValue);
|
||||
}
|
||||
var tmpDataUrl = dstCanvas.toDataURL('image/png');
|
||||
dstImg.src = tmpDataUrl;
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
draw(srcImg, srcArea, dstContext, dstArea, uvFaceWidth, flip)
|
||||
srcImg: the source image to read the skin from
|
||||
srcArea: the area to read
|
||||
dstContext: the context to write the skin to
|
||||
dstArea: dstArea. Set to 'areaNull' to clean the region defined in srcArea
|
||||
uvFaceWidth: uvFaceWidth
|
||||
flip: 0 = normal copy, 1 = flip area horizontally
|
||||
*/
|
||||
function draw(srcImg, srcArea, dstContext, dstArea, uvFaceWidth, flip) {
|
||||
console.log("INFO\tdraw (" + srcArea.x + "/" + srcArea.y + ")-(" + srcArea.w + "/" + srcArea.h + ") (" + dstArea.x + "/" + dstArea.y + ") " + uvFaceWidth + " " + flip);
|
||||
var bsClipX = srcArea.x * uvFaceWidth;
|
||||
var bsClipY = srcArea.y * uvFaceWidth;
|
||||
var bsWidth = srcArea.w * uvFaceWidth;
|
||||
var bsHeight = srcArea.h * uvFaceWidth;
|
||||
var bsDstX = dstArea.x * uvFaceWidth;
|
||||
var bsDstY = dstArea.y * uvFaceWidth;
|
||||
|
||||
if ( (dstArea.w == 0) || (dstArea.h == 0) ) {
|
||||
dstContext.clearRect(bsClipX, bsClipY, bsWidth, bsHeight);
|
||||
} else if (flip == 0) {
|
||||
dstContext.drawImage(srcImg, bsClipX, bsClipY, bsWidth, bsHeight, bsDstX, bsDstY, bsWidth, bsHeight);
|
||||
} else {
|
||||
// use a temporary canvas to flip the element
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = bsWidth;
|
||||
canvas.height = bsHeight;
|
||||
var context = canvas.getContext('2d');
|
||||
context.save();
|
||||
if (flip == 1) {
|
||||
context.translate(bsWidth, 0);
|
||||
context.scale(-1, 1);
|
||||
}
|
||||
context.drawImage(srcImg, bsClipX, bsClipY, bsWidth, bsHeight, 0, 0, bsWidth, bsHeight);
|
||||
context.restore();
|
||||
dstContext.drawImage(canvas, bsDstX, bsDstY, bsWidth, bsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create 3d character (uvcharacter.js)
|
||||
var minecraftCharacter = new MinecraftCharacter('characterElementId');
|
||||
var localScene = minecraftCharacter.scene; // The THREE.js scene
|
||||
var localCharacterCanvas = minecraftCharacter.canvas; // The canvas which contains the UV map of the skin
|
||||
function renderAs(type) {
|
||||
console.log("renderAs(" + type + ")");
|
||||
var showCloth = false;
|
||||
if ( (type == "mcCloth") || (type == "mtCloth") ) {
|
||||
// show cloth
|
||||
showCloth = true;
|
||||
}
|
||||
localScene.getObjectByName(constHat).visible = showCloth;
|
||||
localScene.getObjectByName(constJacket).visible = showCloth;
|
||||
localScene.getObjectByName(constSleeveLeft).visible = showCloth;
|
||||
localScene.getObjectByName(constSleeveRight).visible = showCloth;
|
||||
localScene.getObjectByName(constTrouserLeft).visible = showCloth;
|
||||
localScene.getObjectByName(constTrouserRight).visible = showCloth;
|
||||
|
||||
|
||||
localCharacterCanvas.width = dstCanvas.width;
|
||||
localCharacterCanvas.height = dstCanvas.width; // square
|
||||
var context = localCharacterCanvas.getContext("2d");
|
||||
context.clearRect(0, 0, dstCanvas.width, dstCanvas.width);
|
||||
if ( (type == "mcCharacter") || (type == "mcCloth") ) {
|
||||
// show Minecraft skin
|
||||
if (dstCanvas.width == dstCanvas.height) {
|
||||
// srcImg is Minetest (½ square), dstCanvas was properly build
|
||||
context.drawImage(dstCanvas, 0, 0);
|
||||
} else {
|
||||
// srcImg is Minecraft (square), use it here
|
||||
context.drawImage(srcImg, 0, 0);
|
||||
}
|
||||
}
|
||||
if ( (type == "mtCharacter") || (type == "mtCloth") ) {
|
||||
if (dstCanvas.width == dstCanvas.height) {
|
||||
// srcImg is Minetest (½ square), dstCanvas was properly build
|
||||
context.drawImage(dstCanvas, 0, 0);
|
||||
} else {
|
||||
// srcImg is Minecraft (square), dstCanvas (½ square) contains the Minetest image
|
||||
// copy the Minetest image
|
||||
context.drawImage(dstCanvas, 0, 0);
|
||||
|
||||
// fixme / duplicate code
|
||||
// copy Jacket.back
|
||||
draw(dstCanvas, areaCapeMinetest, context, areaJacket.back, uvFaceWidth, 0);
|
||||
|
||||
// copy and mirror leg: top
|
||||
draw(dstCanvas, areaLegRight.top, context, areaLegLeft.top, uvFaceWidth, 1);
|
||||
// copy and mirror leg: bottom
|
||||
draw(dstCanvas, areaLegRight.bottom, context, areaLegLeft.bottom, uvFaceWidth, 1);
|
||||
// copy and mirror leg: right, front, left
|
||||
draw(dstCanvas, areaLegRight.lfr, context, areaLegLeft.lfr, uvFaceWidth, 1);
|
||||
// copy and mirror leg: back
|
||||
draw(dstCanvas, areaLegRight.back, context, areaLegLeft.back, uvFaceWidth, 1);
|
||||
|
||||
// copy and mirror arm: top
|
||||
draw(dstCanvas, areaArmRight.top, context, areaArmLeft.top, uvFaceWidth, 1);
|
||||
// copy and mirror arm: bottom
|
||||
draw(dstCanvas, areaArmRight.bottom, context, areaArmLeft.bottom, uvFaceWidth, 1);
|
||||
// copy and mirror arm: right, front, left
|
||||
draw(dstCanvas, areaArmRight.lfr, context, areaArmLeft.lfr, uvFaceWidth, 1);
|
||||
// copy and mirror arm: back
|
||||
draw(dstCanvas, areaArmRight.back, context, areaArmLeft.back, uvFaceWidth, 1);
|
||||
}
|
||||
}
|
||||
minecraftCharacter.render;
|
||||
minecraftCharacter.reRender; // update character size manually
|
||||
|
||||
}
|
||||
|
||||
|
||||
//]]>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 447 KiB |
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,216 @@
|
|||
// Global string constants to avoid quoted strings in the code
|
||||
// Body Parts
|
||||
const constHead = "Head";
|
||||
const constHat = "Hat";
|
||||
const constBody = "Body";
|
||||
const constJacket = "Jacket";
|
||||
const constArmLeft = "ArmLeft";
|
||||
const constLegLeft = "LegLeft";
|
||||
const constSleeveLeft = "SleeveLeft";
|
||||
const constTrouserLeft = "TrouserLeft";
|
||||
const constArmRight = "ArmRight";
|
||||
const constLegRight = "LegRight";
|
||||
const constSleeveRight = "SleeveRight";
|
||||
const constTrouserRight = "TrouserRight";
|
||||
const constBodyParts = {
|
||||
1: constHead, 2: constHat, 3: constBody, 4: constJacket,
|
||||
5: constArmLeft, 6: constLegLeft, 7: constSleeveLeft, 8: constTrouserLeft,
|
||||
9: constArmRight, 10: constLegRight, 11: constSleeveRight, 12: constTrouserRight
|
||||
};
|
||||
|
||||
// Faces
|
||||
const constFaceTop = "top";
|
||||
const constFaceBottom = "bottom";
|
||||
const constFaceLeft = "left";
|
||||
const constFaceFront = "front";
|
||||
const constFaceRight = "right";
|
||||
const constFaceBack = "back";
|
||||
const constFacesAll = "all";
|
||||
const constFacesLeftFrontRight = "lfr";
|
||||
const constFaceUnusedL = "unusedL";
|
||||
const constFaceUnusedR = "unusedR";
|
||||
const constBoxFaces = { 1: constFaceRight, 2: constFaceLeft, 3: constFaceTop, 4: constFaceBottom, 5: constFaceFront, 6: constFaceBack };
|
||||
|
||||
// Minimum width of the UV map if each face is represented by a pixel.
|
||||
const constUvWidth = 16;
|
||||
// Default Minecraft skin width: 64px
|
||||
const constDefaultUvImageWidth = 64;
|
||||
// Default face width: 4px (64px/16). Each arm top and bottom face uses 4px². Each head face uses 8px² (2× 2×UvFaceWidth).
|
||||
// Recalculate the uvFaceWidth with the (here unknown) uvImageWidth.
|
||||
var uvFaceWidth = constDefaultUvImageWidth / constUvWidth;
|
||||
|
||||
/* faceArea() defines a rectangular / square face (or area).
|
||||
0/ 0 | 1/0 | 2/0 | ... | 15/0
|
||||
0/ 1 | 1/1 | 2/1 | ... | 15/1
|
||||
...
|
||||
0/15 | 1/15 | 2/15 | ... | 15/15
|
||||
'x' and 'y' define the start position, 'w' and 'h' define the width and height (not the absolute end point) of the area:
|
||||
(2/0)-(2/2) is the head top face
|
||||
Instead of a single face also a larger rectangular / square area with multiple faces may be defined:
|
||||
(0/0)-(15/15) is the whole skin
|
||||
*/
|
||||
function faceArea(x, y, w, h) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
}
|
||||
|
||||
/*
|
||||
UvArea defines a boxed area which contains the relevant skin part of the selected body part. Supported body parts:
|
||||
Head/Hat, Body/Jacket, LArm/LLeg/LTrouser/LSleeve/RArm/RLeg/RTrouser/RSleeve
|
||||
It offers properties to get "all" faces,
|
||||
to get the "top", "bottom", "left", "front", "right" and "back" faces,
|
||||
the "lfr" (Left-Front-Right) and the unused "unusedL" and "unusedR" areas.
|
||||
|
||||
Typical UV definition for each body part of the character are:
|
||||
Head and Hat: w=h=d=2
|
||||
| | w| w|
|
||||
+--+--+--+--+--
|
||||
| |##|##| | d (unusedL, top, bottom, unusedR)
|
||||
| |##|##| |
|
||||
+==+==+==+==+==
|
||||
|##|##|##|##| h (left, front, right, back)
|
||||
|##|##|##|##|
|
||||
+--+--+--+--+--
|
||||
| d| w| d| w|
|
||||
|
||||
Body and Jacket: w=2, h=3 and d=1
|
||||
| | w| w|
|
||||
+-+--+--+-+--
|
||||
| |##|##| | d
|
||||
+=+==+=**=+==
|
||||
|#|##|#|##|
|
||||
|#|##|#|##| h
|
||||
|#|##|#|##|
|
||||
+-+--+--+-+--
|
||||
|d| w|d| w|
|
||||
|
||||
Arms and Legs: w=1, h=3 and d=1
|
||||
| |w|w| |
|
||||
+-+-+-+-+--
|
||||
| |#|#| | d
|
||||
+=+=+=+=+==
|
||||
|#|#|#|#|
|
||||
|#|#|#|#| h
|
||||
|#|#|#|#|
|
||||
+-+--+--+--
|
||||
|d|w|d|w|
|
||||
|
||||
(w = width, h = height, d = depth)
|
||||
*/
|
||||
class UvArea {
|
||||
// supported types:
|
||||
constructor(type) {
|
||||
this.type = type;
|
||||
if ( (this.type == constHead) || (this.type == constHat) ) {
|
||||
// assume constHead
|
||||
this.px = 0;
|
||||
this.py = 0;
|
||||
this.width = 2;
|
||||
this.height = 2;
|
||||
this.depth = 2;
|
||||
if (this.type == constHat) {
|
||||
this.px = 8;
|
||||
this.py = 0;
|
||||
}
|
||||
} else if ( (this.type == constBody) || (this.type == constJacket) ) {
|
||||
// assume constBody
|
||||
this.px = 4;
|
||||
this.py = 4;
|
||||
this.width = 2;
|
||||
this.height = 3;
|
||||
this.depth = 1;
|
||||
if (this.type == constJacket) {
|
||||
this.px = 4;
|
||||
this.py = 8;
|
||||
}
|
||||
} else {
|
||||
// assume constLegRight
|
||||
this.px = 0;
|
||||
this.py = 4;
|
||||
this.width = 1;
|
||||
this.height = 3;
|
||||
this.depth = 1;
|
||||
if (this.type == constArmRight) {
|
||||
this.px = 10;
|
||||
this.py = 4;
|
||||
} else if (this.type == constTrouserRight) {
|
||||
this.px = 0;
|
||||
this.py = 8;
|
||||
} else if (this.type == constSleeveRight) {
|
||||
this.px = 10;
|
||||
this.py = 8;
|
||||
} else if (this.type == constTrouserLeft) {
|
||||
this.px = 0;
|
||||
this.py = 12;
|
||||
} else if (this.type == constLegLeft) {
|
||||
this.px = 4;
|
||||
this.py = 12;
|
||||
} else if (this.type == constArmLeft) {
|
||||
this.px = 8;
|
||||
this.py = 12;
|
||||
} else if (this.type == constSleeveLeft) {
|
||||
this.px = 12;
|
||||
this.py = 12;
|
||||
}
|
||||
}
|
||||
console.log("INFO\tBoxArea " + this.type + " (" + this.px + "/" + this.py + ") (" + this.width + "/" + this.height + "/" + this.depth + ")");
|
||||
}
|
||||
get top() {
|
||||
return new faceArea(this.px + this.depth, this.py, this.width, this.depth);
|
||||
}
|
||||
get bottom() {
|
||||
return new faceArea(this.px + this.depth + this.width, this.py, this.width, this.depth);
|
||||
}
|
||||
get left() {
|
||||
return new faceArea(this.px, this.py + this.depth, this.depth, this.height);
|
||||
}
|
||||
get front() {
|
||||
return new faceArea(this.px + this.depth, this.py + this.depth, this.width, this.height);
|
||||
}
|
||||
get right() {
|
||||
return new faceArea(this.px + this.depth + this.width, this.py + this.depth, this.depth, this.height);
|
||||
}
|
||||
get back() {
|
||||
return new faceArea(this.px + 2*this.depth + this.width, this.py + this.depth, this.width, this.height);
|
||||
}
|
||||
get all() {
|
||||
return new faceArea(this.px, this.py, 2*(this.depth + this.width), this.depth + this.height);
|
||||
}
|
||||
get lfr() {
|
||||
// Left Front Right
|
||||
return new faceArea(this.px, this.py + this.depth, 2*this.depth + this.width, this.height);
|
||||
}
|
||||
get unusedL() {
|
||||
return new faceArea(this.px, this.py, this.depth, this.depth);
|
||||
}
|
||||
get unusedR() {
|
||||
return new faceArea(this.px + this.depth + 2*this.width, this.py, this.depth, this.depth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Global definition of the rectangular or square skin areas
|
||||
// Lines 1-4
|
||||
const areaNull = new faceArea(0, 0, 0, 0); // special, nothing
|
||||
const areaMinetest = new faceArea(0, 0, 16, 8); // special, the whole Mintest skin / the upper half of the Minecraft skin
|
||||
const areaMinecraft = new faceArea(0, 0, 16, 16); // special, the whole Minecraft skin
|
||||
const areaHead = new UvArea(constHead);
|
||||
const areaHat = new UvArea(constHat);
|
||||
// Lines 5-8
|
||||
const areaLegRight = new UvArea(constLegRight);
|
||||
const areaBody = new UvArea(constBody);
|
||||
const areaArmRight = new UvArea(constArmRight);
|
||||
const areaUnusedMinecraft = new faceArea(14, 4, 2, 8); // special unusedL
|
||||
const areaUnusedMinetest = new faceArea(14, 4, 2, 1); // special unusedR
|
||||
const areaCapeMinetest = new faceArea(14, 5, 2, 3); // special, other location than Minecraft:areaJacket.back
|
||||
// Lines 9-12
|
||||
const areaTrouserRight = new UvArea(constTrouserRight);
|
||||
const areaJacket = new UvArea(constJacket);
|
||||
const areaSleeveRight = new UvArea(constSleeveRight);
|
||||
// Lines 13-16
|
||||
const areaTrouserLeft = new UvArea(constTrouserLeft);
|
||||
const areaLegLeft = new UvArea(constLegLeft); // areaLeftLeg
|
||||
const areaArmLeft = new UvArea(constArmLeft);
|
||||
const areaSleeveLeft = new UvArea(constSleeveLeft);
|
|
@ -0,0 +1,248 @@
|
|||
// requires uvareas.js, threee.js
|
||||
|
||||
/*
|
||||
Usage:
|
||||
Make sure to load uvareas.js before uvcharacter.js:
|
||||
<script src="three.min.js" type="text/javascript"></script>
|
||||
<script src="uvareas.js" type="text/javascript"></script>
|
||||
<script src="uvcharacter.js" type="text/javascript"></script>
|
||||
|
||||
Add an element to the HTML page to render the 3D character:
|
||||
<div id="characterElementId"><!-- Placeholder for the 3d Character --></div>
|
||||
|
||||
Create a new instance and supply the element ID of the 3D character:
|
||||
var minecraftCharacter = new MinecraftCharacter('characterElementId');
|
||||
|
||||
Get references to some variables you may want to modify:
|
||||
var localScene = minecraftCharacter.scene; // The THREE.js scene
|
||||
var localCharacterCanvas = minecraftCharacter.canvas; // The canvas which contains the UV map of the skin
|
||||
|
||||
Create or load a UV map and assign it to the canvas:
|
||||
var characterImage = new Image(); // now do something
|
||||
localCharacterCanvas.getContext("2d").drawImage(characterImage, 0, 0);
|
||||
|
||||
Finally call render to show the character:
|
||||
minecraftCharacter.render;
|
||||
*/
|
||||
class MinecraftCharacter {
|
||||
constructor(characterRenderElementIdName) {
|
||||
console.log("MinecraftCharacter(" + characterRenderElementIdName + ")");
|
||||
characterElementId = characterRenderElementIdName;
|
||||
createCharacter();
|
||||
}
|
||||
get canvas() {
|
||||
return characterCanvas;
|
||||
}
|
||||
get scene() {
|
||||
return scene;
|
||||
}
|
||||
|
||||
get render() {
|
||||
characterImageLoaded();
|
||||
return null;
|
||||
}
|
||||
get reRender() {
|
||||
reSetupScene();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Everything below is private even though Javascript does not support this.
|
||||
// Using these vars in other loaded JS files may break this class
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
// canvas to store the skin image
|
||||
var characterCanvas = document.createElement('canvas');
|
||||
|
||||
// element name to display the 3d character in
|
||||
var characterElementId = 'characterElementId';
|
||||
|
||||
// All meshes are stored here so one can access them later
|
||||
var characterMeshes = {};
|
||||
|
||||
// TODO: Move to constructor instead of const
|
||||
const constCharacterScale = 4;
|
||||
|
||||
var isInitialized = false;
|
||||
var radius = 32;
|
||||
var alpha = 0;
|
||||
var renderer; // THREE.WebGLRenderer
|
||||
var camera; // THREE.PerspectiveCamera
|
||||
var scene = new THREE.Scene();
|
||||
|
||||
var skinTexture = new THREE.Texture(characterCanvas);
|
||||
skinTexture.magFilter = THREE.NearestFilter;
|
||||
skinTexture.minFilter = THREE.NearestMipMapNearestFilter;
|
||||
|
||||
// Set the material for character and cloth
|
||||
var materialFigure = new THREE.MeshBasicMaterial({map: skinTexture, side: THREE.FrontSide});
|
||||
var materialCloth = new THREE.MeshBasicMaterial({map: skinTexture, transparent: true, opacity: 1, alphaTest: 0.5, side: THREE.DoubleSide});
|
||||
|
||||
function createCharacter() {
|
||||
// Head Parts
|
||||
// UV Map: https://solutiondesign.com/blog/-/blogs/webgl-and-three-js-texture-mappi-1/
|
||||
/* We have 16x16 areas in the range (0/0)-(1/1). Each area is 0.0625×0.0625.
|
||||
0/0 is bottom left and 1/1 top right */
|
||||
|
||||
var uvFaceWidth = 1/constUvWidth;
|
||||
var i = 0;
|
||||
// scale the character a little lit larger
|
||||
var areaPartScale = 4;
|
||||
for (var part in constBodyParts) {
|
||||
var bodyPart = constBodyParts[part];
|
||||
var areaPart = new UvArea(eval("const" + bodyPart));
|
||||
//console.log(bodyPart, areaPart);
|
||||
|
||||
// Define the geometry
|
||||
// Set the size (Character / Clothes)
|
||||
if ( (bodyPart == constHead) || (bodyPart == constBody) || (bodyPart == constArmLeft) || (bodyPart == constArmRight) || (bodyPart == constLegRight) || (bodyPart == constLegLeft) ) {
|
||||
areaPartScale = constCharacterScale;
|
||||
} else {
|
||||
// The Hat, Jacket, Sleeves and Trousers are a little bit larger
|
||||
areaPartScale = constCharacterScale * 1.125;
|
||||
};
|
||||
var partBox = new THREE.BoxGeometry(areaPartScale * areaPart.width, areaPartScale * areaPart.height, areaPartScale * areaPart.depth, 1, 1, 1);
|
||||
|
||||
// Purge exisiting UV definitions
|
||||
partBox.faceVertexUvs[0] = [];
|
||||
i = 0;
|
||||
// Create new UV definitions for all 6 faces
|
||||
for (var face in constBoxFaces) {
|
||||
var boxFace = constBoxFaces[face];
|
||||
// locate the area on the loaded image with 4 points: Bottom/Left, Bottom/Right, Top/Right, Top/Left
|
||||
// to create a face
|
||||
var partFace = [
|
||||
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y") - eval("areaPart."+boxFace+".h"))),
|
||||
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x") + eval("areaPart."+boxFace+".w")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y") - eval("areaPart."+boxFace+".h"))),
|
||||
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x") + eval("areaPart."+boxFace+".w")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y"))),
|
||||
new THREE.Vector2(uvFaceWidth * (eval("areaPart."+boxFace+".x")), uvFaceWidth * (constUvWidth - eval("areaPart."+boxFace+".y")))
|
||||
];
|
||||
|
||||
// Map the face to the cube. 2 triangles for each rectangle / square
|
||||
partBox.faceVertexUvs[0][i] = [partFace[3], partFace[0], partFace[2]]; i++;
|
||||
partBox.faceVertexUvs[0][i] = [partFace[0], partFace[1], partFace[2]]; i++;
|
||||
}
|
||||
// Select the material (Character or Cloth)
|
||||
var partMesh = null;
|
||||
if ( (bodyPart == constHead) || (bodyPart == constArmLeft) || (bodyPart == constArmRight) || (bodyPart == constLegRight) || (bodyPart == constLegLeft) ) {
|
||||
partMesh = new THREE.Mesh(partBox, materialFigure);
|
||||
} else {
|
||||
partMesh = new THREE.Mesh(partBox, materialCloth);
|
||||
}
|
||||
partMesh.name = String(bodyPart);
|
||||
|
||||
// Move the cube(bodyPart) to the right position to create the figure
|
||||
if ( (bodyPart == constHead) || (bodyPart == constHat) ) {
|
||||
// don't move the Head
|
||||
} else if ( (bodyPart == constBody) || (bodyPart == constJacket) ) {
|
||||
// place the Body under the Head
|
||||
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height/2);
|
||||
} else if ( (bodyPart == constArmLeft) || (bodyPart == constSleeveLeft) ) {
|
||||
// place the Arm next to the Body
|
||||
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height/2);
|
||||
partMesh.position.x = constCharacterScale * (areaBody.width/2 + areaArmLeft.width/2);
|
||||
} else if ( (bodyPart == constArmRight) || (bodyPart == constSleeveRight) ) {
|
||||
// place the Arm next to the Body
|
||||
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height/2);
|
||||
partMesh.position.x = -constCharacterScale * (areaBody.width/2 + areaArmRight.width/2);
|
||||
} else if ( (bodyPart == constLegRight) || (bodyPart == constTrouserRight) ) {
|
||||
// place the Leg under the Body + Head
|
||||
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height + areaLegRight.height/2);
|
||||
partMesh.position.x = -constCharacterScale * (areaLegRight.width/2);
|
||||
} else if ( (bodyPart == constLegLeft) || (bodyPart == constTrouserLeft) ) {
|
||||
// place the Leg under the Body + Head
|
||||
partMesh.position.y = -constCharacterScale * (areaHead.height/2 + areaBody.height + areaLegLeft.height/2);
|
||||
partMesh.position.x = constCharacterScale * (areaLegLeft.width/2);
|
||||
}
|
||||
scene.add(partMesh);
|
||||
characterMeshes[bodyPart] = partMesh;
|
||||
}
|
||||
}
|
||||
|
||||
function characterImageLoaded() {
|
||||
skinTexture.needsUpdate = true;
|
||||
materialFigure.needsUpdate = true;
|
||||
materialCloth.needsUpdate = true;
|
||||
if (isInitialized == false) {
|
||||
isInitialized = true;
|
||||
//createCharacter();
|
||||
setupScene();
|
||||
|
||||
Animate();
|
||||
}
|
||||
}
|
||||
|
||||
function setupScene() {
|
||||
var sceneElement = document.getElementById(characterElementId);
|
||||
var containerRect = sceneElement.getBoundingClientRect();
|
||||
var width = containerRect.width;
|
||||
var height = containerRect.height;
|
||||
|
||||
/*
|
||||
PerspectiveCamera( fov, aspect, near, far )
|
||||
fov — Camera frustum vertical field of view.
|
||||
aspect — Camera frustum aspect ratio.
|
||||
near — Camera frustum near plane.
|
||||
far — Camera frustum far plane.
|
||||
*/
|
||||
camera = new THREE.PerspectiveCamera(75, 1, 1, 10000);
|
||||
camera.position.y = -constCharacterScale * 3;
|
||||
|
||||
renderer = new THREE.WebGLRenderer({alpha: true});
|
||||
renderer.setSize(width, width);
|
||||
|
||||
sceneElement.addEventListener('resize', reSetupScene, false);
|
||||
|
||||
sceneElement.appendChild(renderer.domElement);
|
||||
}
|
||||
function reSetupScene() {
|
||||
if (isInitialized == true) {
|
||||
renderer.setSize(2, 2);
|
||||
var sceneElement = document.getElementById(characterElementId);
|
||||
var containerRect = sceneElement.getBoundingClientRect();
|
||||
var width = containerRect.width;
|
||||
renderer.setSize(width, width);
|
||||
};
|
||||
}
|
||||
function Animate() {
|
||||
requestAnimationFrame(Animate);
|
||||
|
||||
camera.rotation.y = alpha;
|
||||
alpha += Math.PI / 320;
|
||||
camera.position.z = radius*Math.cos(alpha);
|
||||
camera.position.x = radius*Math.sin(alpha);
|
||||
|
||||
var meshLegLeft = characterMeshes[constLegLeft];
|
||||
var meshTrouserLeft = characterMeshes[constTrouserLeft];
|
||||
var meshLegRight = characterMeshes[constLegRight];
|
||||
var meshTrouserRight = characterMeshes[constTrouserRight];
|
||||
var meshArmLeft = characterMeshes[constArmLeft];
|
||||
var meshSleeveLeft = characterMeshes[constSleeveLeft];
|
||||
var meshArmRight = characterMeshes[constArmRight];
|
||||
var meshSleeveRight = characterMeshes[constSleeveRight];
|
||||
var zOffsetLeg = constCharacterScale * areaLegRight.height/2;
|
||||
var yOffsetLeg = constCharacterScale * (areaHead.height/2 + areaBody.height);
|
||||
var zOffsetArm = constCharacterScale * areaArmRight.height/2;
|
||||
var yOffsetArm = constCharacterScale * areaHead.height/2;
|
||||
|
||||
//Leg Swing
|
||||
meshTrouserLeft.rotation.x = meshLegLeft.rotation.x = Math.cos(alpha*4);
|
||||
meshTrouserLeft.position.z = meshLegLeft.position.z = -zOffsetLeg*Math.sin(meshLegLeft.rotation.x);
|
||||
meshTrouserLeft.position.y = meshLegLeft.position.y = -yOffsetLeg - zOffsetLeg*Math.abs(Math.cos(meshLegLeft.rotation.x));
|
||||
meshTrouserRight.rotation.x = meshLegRight.rotation.x = Math.cos(alpha*4 + (Math.PI));
|
||||
meshTrouserRight.position.z = meshLegRight.position.z = -zOffsetLeg*Math.sin(meshLegRight.rotation.x);
|
||||
meshTrouserRight.position.y = meshLegRight.position.y = -yOffsetLeg - zOffsetLeg*Math.abs(Math.cos(meshLegRight.rotation.x));
|
||||
|
||||
//Arm Swing
|
||||
meshSleeveLeft.rotation.x = meshArmLeft.rotation.x = Math.cos(alpha*4 + (Math.PI));
|
||||
meshSleeveLeft.position.z = meshArmLeft.position.z = -zOffsetArm*Math.sin(meshArmLeft.rotation.x);
|
||||
meshSleeveLeft.position.y = meshArmLeft.position.y = -yOffsetArm - zOffsetArm*Math.abs(Math.cos(meshArmLeft.rotation.x));
|
||||
meshSleeveRight.rotation.x = meshArmRight.rotation.x = Math.cos(alpha*4);
|
||||
meshSleeveRight.position.z = meshArmRight.position.z = -zOffsetArm*Math.sin(meshArmRight.rotation.x);
|
||||
meshSleeveRight.position.y = meshArmRight.position.y = -yOffsetArm - zOffsetArm*Math.abs(Math.cos(meshArmRight.rotation.x));
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
Usage:
|
||||
var zoom = new Zooming('elementId');
|
||||
zoom.to('n'); or zoom.to(n);
|
||||
elementId is the id value of an element. All elements are supported, to zoom the whole page use the body.
|
||||
E.g.: <body id="myId"> or <div id="myId"> ... var myZoom = new Zooming('myId');
|
||||
n is the zoom level. Set to lower than 1 to zoom out and higher than 1 to zoom in. Set to 1 for no zoom.
|
||||
E.g.: myZoom.to(1.5s);
|
||||
Known issues:
|
||||
If the image size is specified in "px" and not in "em" zooming does resize the image.
|
||||
*/
|
||||
|
||||
class Zooming {
|
||||
constructor(elementId) {
|
||||
this.body = document.getElementById(elementId);
|
||||
if (this.body.currentStyle) {
|
||||
this.fontSize = body.currentStyle["fontSize"];
|
||||
this.fontUnit = "pt";
|
||||
} else if (window.getComputedStyle) {
|
||||
this.fontSize = 16;
|
||||
this.fontUnit = "px";
|
||||
}
|
||||
this.fontSize = parseFloat(this.fontSize);
|
||||
}
|
||||
to(zoomSize) {
|
||||
this.body.style["fontSize"] = this.fontSize * zoomSize + this.fontUnit;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue