Manually change size of Texture in ThreeJS - javascript

Default wrapS and wrapT is THREE.ClampToEdgeWrapping, which mean, that if I have square picture and geometry like that const geometry = new BoxGeometry(3, 2, 1); I will lose proportion of the source. THREE.RepeatWrapping and THREE.MirroredRepeatWrapping are not suitable for my case, because I need only one image on each side in the center. Is there any way to set width and height of image that is loaded (and maybe change size on each side of сuboid)? The way I'm trying to do it now (not working):
const geometry = new BoxGeometry(9.89326190948486, 0.953460395336151, 3);
const material = new MeshStandardMaterial({
map: new TextureLoader().load(
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iMjU2cHgiIGhlaWdodD0iMjU2cHgiIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWluWU1pbiBtZWV0IiA+PHJlY3QgaWQ9InN2Z0VkaXRvckJhY2tncm91bmQiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBzdHlsZT0iZmlsbDogbm9uZTsgc3Ryb2tlOiBub25lOyIvPjxjaXJjbGUgaWQ9ImUxX2NpcmNsZSIgY3g9IjEyMC4wMCIgY3k9IjExNi4wMCIgc3R5bGU9ImZpbGw6d2hpdGU7c3Ryb2tlOmJsYWNrO3N0cm9rZS13aWR0aDo5cHgiIHI9IjEyNy40NiIgdHJhbnNmb3JtPSJtYXRyaXgoMC43MDcxMDcgMC43MDcxMDcgLTAuNzA3MTA3IDAuNzA3MTA3IDEyOC4xNzIgLTQxLjg3NzIpIi8+PGNpcmNsZSBpZD0iZTJfY2lyY2xlIiBjeD0iMTEzLjAwIiBjeT0iMTEyLjAwIiBzdHlsZT0iZmlsbDpyb3N5YnJvd247c3Ryb2tlOmJsYWNrO3N0cm9rZS13aWR0aDoxcHgiIHI9IjQzLjg2Ii8+PC9zdmc+',
function () {
// texture still lose proportion and behaves as THREE.ClampToEdgeWrapping
material.map.image.height = 3;
material.map.image.width = 3;
material.map.needsUpdate = true;
}),
color: new Color(0.7, 0.7, 0.7)
});
Screenshot of what I got with setting height and width to 128. This thing should be a circle
Screenshot of what I got with setting height and width to 0.9. Also should be a circle.

Related

Extrude 2D Colored Image in Three.js

I've looked for resources online, but I have not seen a way to extrude a colored image in Three.js. I'm trying to create something like a Minecraft item where the image is used to then create an extruded geometry. An example would be: https://minecraft.gamepedia.com/File:BowSpinning3.gif
I've tried looking at this resource: https://muffinman.io/three-js-extrude-svg-path/ but this only extrudes uncolored SVGs.
loader.load('./textures/diamondbleu.svg', function (data) {
// Group we'll use for all SVG paths
const svgGroup = new THREE.Group();
// When importing SVGs paths are inverted on Y axis
// it happens in the process of mapping from 2d to 3d coordinate system
svgGroup.scale.y *= -1;
const material = new THREE.MeshLambertMaterial();
// Loop through all of the parsed paths
data.paths.forEach((path, i) => {
const shapes = path.toShapes(true);
// Each path has array of shapes
shapes.forEach((shape, j) => {
// Finally we can take each shape and extrude it
const geometry = new THREE.ExtrudeGeometry(shape, {
depth: 20,
bevelEnabled: false
});
// Create a mesh and add it to the group
const mesh = new THREE.Mesh(geometry, material);
svgGroup.add(mesh);
});
});
// Get group's size
const box = new THREE.Box3().setFromObject(svgGroup);
const size = new THREE.Vector3();
box.getSize(size);
const yOffset = size.y / -2;
const xOffset = size.x / -2;
// Offset all of group's elements, to center them
svgGroup.children.forEach(item => {
item.position.x = xOffset;
item.position.y = yOffset;
});
svgGroup.position.set(0, blockSize*75, 0);
// Finally we add svg group to the scene
scene.add(svgGroup);
})
Is there a way to modify the code to allow for colored SVGs? Thanks!
You can use the SVGLoader that's available in the "examples/jsm/loaders/" folder.
The docs have outlined how to generate SVGs in 3D space, it looks like your code sample is missing the part where the paths loop makes a new material and assigns a color for each path:
var material = new THREE.MeshBasicMaterial( {
color: path.color,
side: THREE.DoubleSide,
depthWrite: false
} );
Your code seems to create a single LambertMaterial with no colors assigned, and no lights. Lambert materials need lights to be illuminated, whereas BasicMaterial just shows the color without need of lights.
Look at the code in this demo for another example. Instead of using path.color, this demo finds the color by accessing path.userData.style.fill. I think you'll want the latter approach, depending on your SVG file.

Why does setting the position of my mesh not negate the previous translation?

I am creating a simple "Hello World' Three.js application and I am curious to know why this works.
Firstly, I create and show a centered "Hello World" from the code snippet below. This code snippet is responsible for centering the text and moving it back 20 units.
/* Create the scene Text */
let loader = new THREE.FontLoader();
loader.load( 'fonts/helvetiker_regular.typeface.json', function (font) {
/* Create the geometry */
let geometry_text = new THREE.TextGeometry( "Hello World", {
font: font,
size: 5,
height: 1,
});
/* Create a bounding box in order to calculate the center position of the created text */
geometry_text.computeBoundingBox();
let x_mid = geometry_text.boundingBox.max.x - geometry_text.boundingBox.min.x;
geometry_text.translate(-0.5 * x_mid, 0, 0); // Center the text by offsetting half the width
/* Currently using basic material because I do not have a light, Phong will be black */
let material_text = new THREE.MeshBasicMaterial({
color: new THREE.Color( 0x006699 )
});
let textMesh = new THREE.Mesh(geometry_text, material_text);
textMesh.position.set(0, 0, -20);
//debugger;
scene.add(textMesh);
console.log('added mesh')
} );
Now notice here that I perform the translation first
geometry_text.computeBoundingBox();
let x_mid = geometry_text.boundingBox.max.x - geometry_text.boundingBox.min.x;
geometry_text.translate(-0.5 * x_mid, 0, 0);
and then the position is performed to move the mesh
let textMesh = new THREE.Mesh(geometry_text, material_text);
textMesh.position.set(0, 0, -20);
Now my confusion comes from that fact that if I remove my translation, then my "Hello World" text is not centered. However after my translation is completed, I am setting the position on my mesh to (0, 0, -20), shouldn't this set_position call overwrite my previous translation and move the object to the position (0, 0, -20), why is my text still centered eventhough my set_position is called after my translation?
This is because the call to THREE.TextGeometry.translate() ends up calling THREE.Geometry.applyMatrix() with the corresponding translation matrix, which bakes the transformation by directly modifying the vertex coordinates. See Geometry.js#L149 for the source.
In other words, before the call
textMesh.position.set(0, 0, -20);
the mesh transformation matrix was still the identity matrix. Mesh transformation differs from geometry transformation in that it only updates the matrix that is passed into the shader, instead of recomputing every vertex. For which one you would want to use: transforming the geometry is more expensive, but you can do it once and avoid it in the render loop (See the explanation here).

How to render and set custom mipmaps for THREE.WebGLRenderTarget

I'd like to generate custom mip maps for a rendered frame for an effect in THREE.js, but it's not clear how to achieve that. I'll want to regenerate the mip maps again after every frame.
This example shows how to generate mipmaps for a texture, but it looks like the mipmaps array requires a handle to an image or canvas tag, which a renderTarget texture does not have. Ideally I'd like to be able to do something like this:
var rt = new THREE.WebGLRenderTarget()
// initialize and render to the target
var mipmaps = [];
var width = rt.width / 2;
var height = rt.height / 2;
while( width > 2 && height > 2) {
var mip = new THREE.WebGLRenderTarget({ width, height });
mipmaps.push(mip.texture); // mip.texture.image === undefined
// render to the new mipmap with a custom downsample shader
width /= 2;
height /= 2;
}
rt.texture.mipmaps = mipmaps;
// render using the original render target as a texture map and using
// the new mip maps as needed
Is this possible? How would I achieve this?
Thank you!

How to get texture dimensions with Three.js

I would like to know wether it is possible to get the dimensions of a texture ?
I used this line to set my texture :
const texture = THREE.ImageUtils.loadTexture(src)
Maybe it is necessary to load the image to get its dimensions (but is it possible only with javascript, not html and its div ?) ?
I would like the material I create afterwards to fit the texture dimensions.
First, THREE.ImageUtils.loadTexture is deprecated in the latest THREE.js (r90). Take a look at THREE.TextureLoader instead.
That said, you can get to the image and its properties from a loaded texture.
texture.image
Depending on the image format, you should be able to access the width/height properties, which will be your texture's dimensions.
Just a note: Loading a texture is asynchronous, so you'll need to define the onLoad callback.
var loader = new THREE.TextureLoader();
var texture = loader.load( "./img.png", function ( tex ) {
// tex and texture are the same in this example, but that might not always be the case
console.log( tex.image.width, tex.image.height );
console.log( texture.image.width, texture.image.height );
} );
If you turn off sizeAttenuation, and you have a function that scales the Sprite according to the desired width, then this function will be like:
scaleWidth(width) {
const tex = sprite.material.map;
const scaleY = tex.image.height / tex.image.width;
sprite.scale.setX(width).setY(width * scaleY);
}
So, at this point, you can set the scale according to the desired width, maintaining the aspectRatio of the image.
Then you must have a function that receives the camera, and depending on the type of camera, updates the sprite's width:
updateScale(cam) {
let cw = 1;
if(cam.isOrthographicCamera) {
cw = cam.right - cam.left;
}
else if(cam.isPerspectiveCamera) {
cw = 2 * cam.aspect * Math.tan(cam.fov * 0.5 * 0.01745329);
}
scaleWidth(cw * desiredScaleFactor);
}

2D top-down minimap

I'd like to make a minimap of my rpg game.
Is making a minimap as simple as dividing all object dimensions, velocities, and coordinates by however large you want the minimap?
For example below... You have a size of 1000x1000px, a canvas (viewport) of 500x500px, the player is located in the center of the viewport... If you wanted a minimap half the size of the actual world, you would do:
Player/Viewport x,y velocity/2
Player/Viewport x,y coordinates/2
Canvas, world, and all objects' width and height are divided by 2
etc...
That way the rendering of the minimap on the world and the velocities are scaled accurately? Am I missing anything?
Thanks!
EDIT: Something like this?
function miniMap() {
$(".minimapHolder").show();
$("#mini_map").text("hide minimap");
var minicanvas = document.getElementById("miniMap");
ministage = new createjs.Stage("miniMap");
minicam = new createjs.Shape();
minicam.graphics.beginStroke("white").drawRoundRect(0, 0, 100, 40, 5);
//blip representation of Player
player_blip = new createjs.Shape();
player_blip.graphics.beginFill("yellow").drawRoundRect(0, 0, 11.2, 12, 1);
animal_blip = new createjs.Shape();
animal_blip.graphics.beginFill("red").drawRoundRect(0, 0, 24.4, 21.6, 1);
player_blip.x = players_Array[0].x/5;
player_blip.y = players_Array[0].y/5;
animal_blip.x = animalContainer.x/5;
animal_blip.y = animalContainer.y/5;
minicam.x = players_Array[0].x-110;
minicam.y = players_Array[0].y-110;
ministage.addChild(player_blip, animal_blip, minicam);
ministage.update();
}
function updateMiniMap() {
player_blip.x = players_Array[0].x/5;
player_blip.y = players_Array[0].y/5;
if (ContainerOfAnimals.children[0] != null) {
var pt = ContainerOfAnimals.localToGlobal(ContainerOfAnimals.children[0].x, ContainerOfAnimals.children[0].y);
console.log(pt.x);
animal_blip.x = pt.x/5;
animal_blip.y = pt.y/5;
} else {
ministage.removeChild(animal_blip);
}
minicam.x = player_blip.x-40;
minicam.y = player_blip.y-15;
ministage.update();
}
Gives:
Short anwswer: "It will(most likely) work." ... but:
What you are trying to achieve is just scaling the stage/container, so you could also just use a copy of everything and put it into a container and scale it down to 0.5, but that is not the purpose of a minimap.
Objects of the minimap should only be a representation of the object in the 'real' world and should therefore not have any velocity ect.(that should especially not be updated separately from the 'real' world) - while your approach will probably work, you'd allways have to keep track and update every property, this will get messy quickly or even lead to differences if you miss some tiny things.
A more 'clean'(and simple) approach to this would be, that each minimap-object has a reference to the object in the 'real' world and on each tick, it just reads the x/y-coordinates and updates its' own coordinates based on the minimap-scale.
Another thing is the graphics: Scaling-operations can be costly(performance wise), especially when they are done each frame, so IF you use the same graphics for the minimap you should at least used a cached DisplayObject and not have the graphics scaled each frame.

Categories

Resources