mson-three/src/three/uv.ts

277 lines
7.3 KiB
TypeScript

import {
BoxGeometry,
BufferAttribute,
PlaneGeometry,
Vector2,
Vector3,
Vector4,
} from 'three';
import { MsonFace, MsonTexture } from '../mson';
import { convertRange } from '../util/num-range';
export class UVMapper {
// px nx py ny pz nz
static boxFaceOrder = ['west', 'east', 'up', 'down', 'north', 'south'];
static boxWidthSide: Record<string, 'x' | 'y' | 'z'> = {
east: 'z',
west: 'z',
up: 'x',
down: 'x',
south: 'x',
north: 'x',
};
static boxHeightSide: Record<string, 'x' | 'y' | 'z'> = {
east: 'y',
west: 'y',
up: 'z',
down: 'z',
south: 'y',
north: 'y',
};
static planeWidthSide: Record<string, 'x' | 'y'> = {
east: 'x',
west: 'x',
up: 'x',
down: 'x',
south: 'x',
north: 'x',
};
static planeHeightSide: Record<string, 'x' | 'y'> = {
east: 'y',
west: 'y',
up: 'y',
down: 'y',
south: 'y',
north: 'y',
};
/**
* @param sizeU Size of quad U
* @param sizeV Size of quad V
* @param txU Texture width
* @param txV Texture height
* @param setU Specified U offset
* @param setV Specified V offset
* @param invertU Invert U axis
* @param invertV Invert V axis
*/
static uvOffsetConvert(
sizeU: number,
sizeV: number,
txU: number,
txV: number,
setU: number = 0,
setV: number = 0,
invertU = false,
invertV = false,
) {
/*
* UV coordinate system
* 0,1 1,1
* V
* |
* |
* |
* |
* +-----------U
* 0,0 1,0
*/
/*
* txU equivalent to 1
* txV equivalent to 1
*
* 0 txU
* 0 -------------------- 1
* | |
* setU +sizeU
* u1 u2
*/
const uStart = convertRange(setU, [0, txU], [0, 1]);
const uEnd = convertRange(setU + sizeU, [0, txU], [0, 1]);
const vStart = 1 - convertRange(setV, [0, txV], [0, 1]);
const vEnd = 1 - convertRange(setV + sizeV, [0, txV], [0, 1]);
return new Vector4(
invertU ? uEnd : uStart,
invertU ? uStart : uEnd,
invertV ? vEnd : vStart,
invertV ? vStart : vEnd,
);
}
static unwrapBox(size: Vector3) {
const widths = UVMapper.boxFaceOrder.map(
(item) => size[UVMapper.boxWidthSide[item]],
);
const heights = UVMapper.boxFaceOrder.map(
(item) => size[UVMapper.boxHeightSide[item]],
);
const layout: any = {
up: { x: 'west', y: 0 },
down: { x: 'up', y: 0 },
west: { x: 0, y: 'up' },
south: { x: 'west', y: 'up' },
east: { x: 'south', y: 'down' },
north: { x: 'east', y: 'down' },
};
Object.keys(layout).forEach((key) => {
const entry = layout[key];
if (typeof entry.x === 'string') {
const prev = layout[entry.x];
entry.x = prev.x + widths[UVMapper.boxFaceOrder.indexOf(entry.x)];
}
if (typeof entry.y === 'string') {
const prev = layout[entry.y];
entry.y = prev.y + heights[UVMapper.boxFaceOrder.indexOf(entry.y)];
}
});
return layout;
}
static mapBoxUVs(
size: Vector3,
geometry: BoxGeometry,
texture?: MsonTexture,
mirror?: boolean | [boolean, boolean, boolean],
) {
const uvAttribute = geometry.getAttribute('uv') as BufferAttribute;
const index = geometry.getIndex() as BufferAttribute;
const uv = new Vector2();
const layout = UVMapper.unwrapBox(size);
let textureWidth = texture?.w ?? 64;
let textureHeight = texture?.h ?? 64;
let textureOffsetU = texture?.u ?? 0;
let textureOffsetV = texture?.v ?? 0;
const invU: string[] = ['up'];
const invV: string[] = ['up'];
const mirroring =
mirror !== undefined
? Array.isArray(mirror)
? mirror
: [mirror, mirror, mirror]
: [false, false, false];
// TODO: figure out what rest of the components mirror
const [flipX, flipY, flipZ] = mirroring;
if (flipX) {
invU.push('south', 'north');
}
// px nx py ny pz nz
// clockwise count
for (let i = 0; i < index!.count; i += 3) {
const faceName = UVMapper.boxFaceOrder[Math.floor(i / 6)];
const triIndex = Math.ceil((i / 6) % 1);
const sizeU = size[UVMapper.boxWidthSide[faceName]];
const sizeV = size[UVMapper.boxHeightSide[faceName]];
const addVOffset = layout[faceName].y;
const addUOffset = layout[faceName].x;
let finalU = textureOffsetU + addUOffset;
let finalV = textureOffsetV + addVOffset;
const faceCoords = UVMapper.uvOffsetConvert(
sizeU,
sizeV,
textureWidth,
textureHeight,
finalU,
finalV,
invU.includes(faceName),
invV.includes(faceName),
);
for (let attrib = 0; attrib < 3; attrib++) {
const a = index?.getX(i + attrib);
uv.fromBufferAttribute(uvAttribute, a);
// -> bottom left, top left, bottom right
if (triIndex === 0) {
if (attrib === 0) uv.set(faceCoords.x, faceCoords.z);
if (attrib === 1) uv.set(faceCoords.x, faceCoords.w);
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
}
// -> top left, top right, bottom right
if (triIndex === 1) {
if (attrib === 0) uv.set(faceCoords.x, faceCoords.w);
if (attrib === 1) uv.set(faceCoords.y, faceCoords.w);
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
}
uvAttribute.setXY(a, uv.x, uv.y);
}
}
}
static mapPlanarUvs(
face: MsonFace,
size: Vector2,
geometry: PlaneGeometry,
texture?: MsonTexture,
mirror?: boolean | [boolean, boolean],
) {
const uvAttribute = geometry.getAttribute('uv') as BufferAttribute;
const index = geometry.getIndex() as BufferAttribute;
const uv = new Vector2();
let textureWidth = texture?.w ?? 64;
let textureHeight = texture?.h ?? 64;
let textureOffsetU = texture?.u ?? 0;
let textureOffsetV = texture?.v ?? 0;
const mirroring =
mirror !== undefined
? Array.isArray(mirror)
? mirror
: [mirror, mirror]
: [false, false];
for (let i = 0; i < index!.count; i += 3) {
const triIndex = Math.ceil((i / 6) % 1);
const sizeU = size[UVMapper.planeWidthSide[face]];
const sizeV = size[UVMapper.planeHeightSide[face]];
let finalU = textureOffsetU;
let finalV = textureOffsetV;
const faceCoords = UVMapper.uvOffsetConvert(
sizeU,
sizeV,
textureWidth,
textureHeight,
finalU,
finalV,
!mirroring[0],
mirroring[1],
);
for (let attrib = 0; attrib < 3; attrib++) {
const a = index?.getX(i + attrib);
uv.fromBufferAttribute(uvAttribute, a);
// -> bottom left, top left, bottom right
if (triIndex === 0) {
if (attrib === 0) uv.set(faceCoords.x, faceCoords.z);
if (attrib === 1) uv.set(faceCoords.x, faceCoords.w);
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
}
// -> top left, top right, bottom right
if (triIndex === 1) {
if (attrib === 0) uv.set(faceCoords.x, faceCoords.w);
if (attrib === 1) uv.set(faceCoords.y, faceCoords.w);
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
}
uvAttribute.setXY(a, uv.x, uv.y);
}
}
}
}