-
-
Save sadernalwis/f6d7c28b90cc93c104b75c040bad3363 to your computer and use it in GitHub Desktop.
Fit, fill, stretch a texture on a mesh when given the mesh's aspect ratio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @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