Skip to content

Instantly share code, notes, and snippets.

@bartwttewaall
Last active June 6, 2023 15:11
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bartwttewaall/5a1168d04a07d52eaf0571f7990191c2 to your computer and use it in GitHub Desktop.
Save bartwttewaall/5a1168d04a07d52eaf0571f7990191c2 to your computer and use it in GitHub Desktop.
Fit, fill, stretch a texture on a mesh when given the mesh's aspect ratio
import {
Object3D,
Cache,
Texture,
PlaneGeometry,
MeshLambertMaterial,
Mesh,
ClampToEdgeWrapping,
RepeatWrapping,
MirroredRepeatWrapping,
RGBFormat,
RGBAFormat
} from 'three/build/three.module';
import { fitTexture } from './fitTexture';
/**
* Testcase on how to manipulate a texture's uv offset and scale in various modes
*/
export class FitFillTest extends Object3D {
constructor() {
super();
this.size = 20;
this.offset = 50;
const portraitTexture1 = this.createTexture('/assets/images/portrait.jpg');
const portraitTexture2 = this.createTexture('/assets/images/portrait.jpg');
const portraitTexture3 = this.createTexture('/assets/images/portrait.jpg');
const landscapeTexture1 = this.createTexture('/assets/images/landscape.jpg');
const landscapeTexture2 = this.createTexture('/assets/images/landscape.jpg');
const landscapeTexture3 = this.createTexture('/assets/images/landscape.jpg');
const squareTexture1 = this.createTexture('/assets/images/square.jpg');
const squareTexture2 = this.createTexture('/assets/images/square.jpg');
const squareTexture3 = this.createTexture('/assets/images/square.jpg');
// const objectFit = 'stretch';
// const objectFit = 'fit';
const objectFit = 'fill';
const mesh1 = this.createMesh(portraitTexture1, 2 / 1, objectFit, 1);
mesh1.position.set(-this.offset, this.offset / 2, 0);
this.add(mesh1);
const mesh2 = this.createMesh(portraitTexture2, 1 / 2, objectFit, 2);
mesh2.position.set(-this.offset, 0, 0);
this.add(mesh2);
const mesh3 = this.createMesh(portraitTexture3, 1, objectFit, 3);
mesh3.position.set(-this.offset, -this.offset / 2, 0);
this.add(mesh3);
const mesh4 = this.createMesh(landscapeTexture1, 2 / 1, objectFit, 4);
mesh4.position.set(0, this.offset / 2, 0);
this.add(mesh4);
const mesh5 = this.createMesh(landscapeTexture2, 1 / 2, objectFit, 5);
mesh5.position.set(0, 0, 0);
this.add(mesh5);
const mesh6 = this.createMesh(landscapeTexture3, 1, objectFit, 6);
mesh6.position.set(0, -this.offset / 2, 0);
this.add(mesh6);
const mesh7 = this.createMesh(squareTexture1, 2 / 1, objectFit, 7);
mesh7.position.set(this.offset, this.offset / 2, 0);
this.add(mesh7);
const mesh8 = this.createMesh(squareTexture2, 1 / 2, objectFit, 8);
mesh8.position.set(this.offset, 0, 0);
this.add(mesh8);
const mesh9 = this.createMesh(squareTexture3, 1, objectFit, 9);
mesh9.position.set(this.offset, -this.offset / 2, 0);
this.add(mesh9);
}
createTexture(url) {
const imageSource = Cache.get(url);
var isJPEG = url.search(/\.jpe?g($|\?)/i) > 0 || url.search(/^data\:image\/jpeg/) === 0;
var texture = new Texture();
texture.image = imageSource;
texture.wrapS = ClampToEdgeWrapping;
texture.wrapT = ClampToEdgeWrapping;
// texture.wrapS = RepeatWrapping;
// texture.wrapT = RepeatWrapping;
// texture.wrapS = MirroredRepeatWrapping;
// texture.wrapT = MirroredRepeatWrapping;
texture.format = isJPEG ? RGBFormat : RGBAFormat;
texture.needsUpdate = true;
return texture;
}
createMesh(texture, screenAspect, objectFit, id) {
const width = this.size / screenAspect;
const height = this.size;
const geo = new PlaneGeometry(width, height, 8, 8);
const mat = new MeshLambertMaterial({ map: texture, wireframe: false });
// if ([1, 4, 7].includes(id)) {
// if ([2, 5, 8].includes(id)) {
// if ([3, 6, 9].includes(id)) {
this.fitTexture(mat.map, width / height, objectFit);
// }
return new Mesh(geo, mat);
}
/**
* @param {Texture} texture - a texture containing a loaded image with a defined width and height
* @param {number} screenAspect - the aspect ratio (width / height) of the model that contains the texture
* @param {"fit"|"fill"|"stretch"} mode - three modes of manipulating the texture offset and scale
* @param {number} [alignH] - optional multiplier to align the texture horizontally - 0: left, 0.5: center, 1: right
* @param {number} [alignV] - optional multiplier to align the texture vertically - 0: bottom, 0.5: middle, 1: top
**/
fitTexture(texture, screenAspect, mode, alignH = 0.5, alignV = 0.5) {
const imageAspect = texture.image.width / texture.image.height;
const scale = imageAspect / screenAspect;
const offsetX = (imageAspect - screenAspect) / imageAspect;
const offsetY = (screenAspect - imageAspect) / screenAspect;
switch (mode) {
case 'contain':
case 'fit': {
if (screenAspect < imageAspect) {
texture.offset.set(0, offsetY * alignV);
texture.repeat.set(1, scale);
} else {
texture.offset.set(offsetX * alignH, 0);
texture.repeat.set(1 / scale, 1);
}
break;
}
case 'cover':
case 'fill': {
if (screenAspect < imageAspect) {
texture.offset.set(offsetX * alignH, 0);
texture.repeat.set(1 / scale, 1);
} else {
texture.offset.set(0, offsetY * alignV);
texture.repeat.set(1, scale);
}
break;
}
case 'none':
case 'stretch':
default: {
texture.offset.set(0, 0);
texture.repeat.set(1, 1);
break;
}
}
}
}
/**
* @param {Texture} texture - a texture containing a loaded image with a defined width and height
* @param {number} screenAspect - the aspect ratio (width / height) of the model that contains the texture
* @param {"fit"|"fill"|"stretch"} mode - three modes of manipulating the texture offset and scale
* @param {number} [alignH] - optional multiplier to align the texture horizontally - 0: left, 0.5: center, 1: right
* @param {number} [alignV] - optional multiplier to align the texture vertically - 0: bottom, 0.5: middle, 1: top
**/
export function fitTexture(texture, screenAspect, mode, alignH = 0.5, alignV = 0.5) {
const imageAspect = texture.image.width / texture.image.height;
const scale = imageAspect / screenAspect;
const offsetX = (imageAspect - screenAspect) / imageAspect;
const offsetY = (screenAspect - imageAspect) / screenAspect;
switch (mode) {
case 'contain':
case 'fit': {
if (screenAspect < imageAspect) {
texture.offset.set(0, offsetY * alignV);
texture.repeat.set(1, scale);
} else {
texture.offset.set(offsetX * alignH, 0);
texture.repeat.set(1 / scale, 1);
}
break;
}
case 'cover':
case 'fill': {
if (screenAspect < imageAspect) {
texture.offset.set(offsetX * alignH, 0);
texture.repeat.set(1 / scale, 1);
} else {
texture.offset.set(0, offsetY * alignV);
texture.repeat.set(1, scale);
}
break;
}
case 'none':
case 'stretch':
default: {
texture.offset.set(0, 0);
texture.repeat.set(1, 1);
break;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment