I'm trying to create a textured surface along a path in three.js. I want the texture to tile/repeat along the direction of the path, like this example created in blender:
One way to accomplish this is to create each of the faces "by hand" and to apply a material/texture to each. That works out fine for simple paths (e.g. straight lines) but gets complicated for more elaborate paths.
One of the tools three.js provides is ExtrudeGeometry. Applying a texture to a mesh created this way looks like this with the default UV mapping:
So clearly I need to write a custom UVGenerator function to pass to ExtrudeGeometry. Unfortunately, this doesn't appear to be something for which there is documentation, and previous questions that show up in search results are either out of date (the most complete answers involve a different API for the UVGenerator functions: example) or have no answers (an example).
Here's a jsFiddle example that illustrates the undesired/default behavior. The code is reproduced below. The uvGenerator() function in it is functionally identical to the default three.js uvGenerator, THREE.WorldUVGenerator. It's in the example just to make it easier to fiddle with.
window.onload = function() {
var camera, dataURI, renderer, scene, surface;
var texture, uvGenerator;
var width = 800, height = 600;
dataURI = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAi0lEQVRIx83WQQ6AIAwEwMXwb/p0b4SI1CJsS08e2k0nKDGVUsCsDEBESOkikusTIx3ABXJdJEEN9BLsRbRRjoJdiEeIr2Ad0Y+7C1YQr4MRgn+I0UiQYBahNMcJ7Ai9LVRgQXw2RAv0HS2HdIBgtKnxHTtD0O9r/86PEbRbT11TdEHecukrldi/7zd8KDG/sdhdzgAAAABJRU5ErkJggg==';
function initCamera() {
camera = new THREE.PerspectiveCamera(60, width/height, 1, 1000);
camera.position.set(0, 0, 0);
scene.add(camera);
}
function initLights() {
var lights;
lights = [
new THREE.PointLight(0xffffff, 1, 0),
new THREE.PointLight(0xffffff, 1, 0),
new THREE.PointLight(0xffffff, 1, 0),
];
lights[0].position.set(0, 200, 0);
lights[1].position.set(100, 200, 100);
lights[2].position.set(-100, -200, -100);
scene.add(lights[0]);
scene.add(lights[1]);
scene.add(lights[2]);
}
function initRenderer() {
var canvas;
canvas = document.getElementById('canvas');
renderer = new THREE.WebGLRenderer();
renderer.setSize(width, height);
canvas.appendChild(renderer.domElement);
}
function initScene() {
scene = new THREE.Scene();
}
function initSurface() {
surface = extrudeSurface();
surface.position.set(50, -50, 50);
scene.add(surface);
}
function generalInit() {
initScene();
initCamera();
initLights();
initSurface();
initRenderer();
animate();
}
uvGenerator = {
generateTopUV: function(geometry, vertices, idxA, idxB, idxC) {
var ax, ay, bx, by, cx, cy;
ax = vertices[idxA * 3];
ay = vertices[(idxA * 3) + 1];
bx = vertices[idxB * 3];
by = vertices[(idxB * 3) + 1];
cx = vertices[idxC * 3];
cy = vertices[(idxC * 3) + 1];
return([
new THREE.Vector2(ax, ay),
new THREE.Vector2(bx, by),
new THREE.Vector2(cx, cy),
]);
},
generateSideWallUV: function(geometry, vertices,
idxA, idxB, idxC, idxD) {
var ax, ay, az, bx, by, bz, cx, cy, cz;
var dx, dy, dz, bb, bbx, bby, bbz;
geometry.computeBoundingBox();
bb = geometry.boundingBox;
bbx = bb.max.x - bb.min.x;
bby = bb.max.y - bb.min.y;
bbz = bb.max.z - bb.min.z;
ax = vertices[idxA * 3];
ay = vertices[(idxA * 3) + 1];
az = vertices[(idxA * 3) + 2];
bx = vertices[idxB * 3];
by = vertices[(idxB * 3) + 1];
bz = vertices[(idxB * 3) + 2];
cx = vertices[idxC * 3];
cy = vertices[(idxC * 3) + 1];
cz = vertices[(idxC * 3) + 2];
dx = vertices[idxD * 3];
dy = vertices[(idxD * 3) + 1];
dz = vertices[(idxD * 3) + 2];
if(Math.abs(ay - by) < 0.01) {
return([
new THREE.Vector2(ax, 1 - az),
new THREE.Vector2(bx, 1 - bz),
new THREE.Vector2(cx, 1 - cz),
new THREE.Vector2(dx, 1 - dz),
]);
} else {
return([
new THREE.Vector2(ay, 1 - az),
new THREE.Vector2(by, 1 - bz),
new THREE.Vector2(cy, 1 - cz),
new THREE.Vector2(dy, 1 - dz),
]);
}
},
}
function extrudeSurface() {
var extrudeCfg, geometry, material, mesh, size, shape, curve;
size = 20;
curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-50, 0, -25),
new THREE.Vector3(50, 0, 75),
]);
extrudeCfg = {
steps: 200,
bevelEnabled: false,
extrudePath: curve,
UVGenerator: uvGenerator,
//UVGenerator: THREE.WorldUVGenerator,
};
shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(0, size);
shape.lineTo(0.1, size);
shape.lineTo(0.1, 0);
shape.lineTo(0, 0);
geometry = new THREE.ExtrudeGeometry(shape, extrudeCfg);
geometry.computeBoundingBox();
geometry.computeVertexNormals(true);
material = new THREE.MeshBasicMaterial({ map: texture });
mesh = new THREE.Mesh(geometry, material);
new THREE.Vector3(0, 0, 0),
return(mesh);
}
function animate() {
if(!scene)
return;
animID = requestAnimationFrame(animate);
render();
update();
}
function render() {
if(!scene || !camera || !renderer)
return;
renderer.render(scene, camera);
}
function update() {
if(!scene || !camera || !surface)
return;
camera.lookAt(surface.position);
//surface.rotation.x += 0.01;
surface.rotation.y += 0.01;
}
function loadTexture() {
var loader;
loader = new THREE.TextureLoader();
loader.load(dataURI,
function(t) {
t.wrapS = THREE.RepeatWrapping;
t.wrapT = THREE.RepeatWrapping;
t.magFilter = THREE.NearestFilter;
t.minFilter = THREE.NearestFilter;
t.repeat.set(1/20, 1/20);
texture = t;
generalInit();
}
);
}
loadTexture();
};
I've tried converting the vertex coords into a UV-ish 0-1 range by dividing by the size of the geometry's bounding box, but this does not produce the desired result. E.g. something of the form (here only shown for one of the return values):
generateSideWallUV: function(geometry, vertices, idxA, idxB, idxC, idxD) {
var ax = vertices[idxA * 3];
geometry.computeBoundingBox();
var bb = geometry.boundingBox;
var bbx = bb.max.x - bb.min.x;
var bbz = bb.max.z - bb.min.z;
...
return([
new THREE.Vector2(ax / bbx, 1 - (az / bbz),
...
]);
}
}
This approach not working makes sense, because what we care about is the position of the vertex as a fraction of the length of the extruded path, not as a fraction of the bounding box. But I don't know and have not been able to look up how to get that information out of the geometry, vertices, or anything else documented in THREE.
Any help/pointers would be appreciated.
Correction: Dividing the vertex coords by the size of the bounding box also doesn't work in this case because when ExtrudeGeometry calls generateSideWallUV() the bounding box is always
min":{"x":null,"y":null,"z":null},"max":{"x":null,"y":null,"z":null}}
...which means anything/(max.x - min.x) will always evaluate as infinite.
So now I'm even more confused about what we can hope to accomplish in a custom UV generator.
If I'm missing something obvious (for example, if I shouldn't be using ExtrudeGeometry for this sort of thing at all) I'd love to be educated.
Answering my own question:
Here's a link to a jsFiddle of the solution.
Here's the interesting parts. First, instead of using THREE.RepeatWrapping we use ClampToEdgeWrapping and get rid of the repeat setting:
t.wrapS = THREE.ClampToEdgeWrapping;
t.wrapT = THREE.ClampToEdgeWrapping;
//t.repeat.set(1 / 20, 1/20);
Then when we create the config object to pass to ExtrudeGeometry we set the steps to be exactly the number of faces we want. This is kinda a kludge because it seems like we shouldn't have to make a decision about the geometry of the object just to get our UVs right--there are plenty of cases where we might have bends/twists were we probably want more vertices to avoid folding/tearing/just looking wonky. But eh, I'm willing to worry about that later. Anyway, our updated extrudeCfg (in extrudeSurface() in the example) becomes:
extrudgeCfg = {
steps: 20,
bevelEnabled: false,
extrudePath: curve,
UVGenerator: uvGenerator,
};
And then finally we re-write our UVGenerator. And since we're now clamping the texture and using a smaller number of faces, we can do something painfully simple like:
generateSideWallUV: function(geometry, vertices, idxA, idxB, idxC, idxD) {
return([
new THREE.Vector2(0, 0),
new THREE.Vector2(1, 0),
new THREE.Vector2(1, 1),
new THREE.Vector2(0, 1),
]);
}
...which is to say we just stretch a copy of the texture across each face (with the only complication being because the sides of the extruded geometry are a rectangle consisting of two triangles instead of a single quad).
Et voilà:
Related
For my current task, I am making a 2D scatterplot with three.js, and need to make the points different sizes. Here is my current implementation. I can't figure out how to make the sizes of the points different and derived from the "radius" feature. Additionally, I want to make a small black border around every point that is the same thickness regardless of size, like this. For these types of features, I've seen that vertexShaders are traditionally used, but I want to render an almost static image and nothing needs to change during runtime. Here is relevant code:
var coordinates = new Float32Array(data_points.length*3);
var colors = new Float32Array(data_points.length*3);
var sizes = new Float32Array(data_points.length);
for (var i=0; i<data_points.length; i++) {
// Set vector coordinates from data
let vertex = new THREE.Vector3(data_points[i].x, data_points[i].y, 0);
let color = new THREE.Color(color_array[data_points[i].label]);
vertex.toArray(coordinates, i*3);
color.toArray(colors, i*3);
sizes[i] = data_points[i].radius*100;
}
let geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(coordinates, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
geometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1 ));
let pointsMaterial = new THREE.PointsMaterial({
size: 100,
sizeAttenuation: true,
vertexColors: THREE.VertexColors,
map: new THREE.TextureLoader().load("https://fastforwardlabs.github.io/visualization_assets/circle-sprite.png"),
transparent: true
});
let points = new THREE.Points(geometry, pointsMaterial);
This is a relatively easy problem, but I'm not experienced with javascript and could use any help I can get. Thank you so much!
In order to use Points, you are most likely looking at needing to make a custom shader (vertex+fragment).
But, as a thought, is your data so large that you couldn't use meshes instead? A combination of scaled CircleBufferGeometry as a Mesh and a Line (or fat lines) could do exactly what you want.
let W = window.innerWidth;
let H = window.innerHeight;
let aspect = W / H;
const frustumSize = 100;
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(frustumSize * aspect / -2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, 1, 100);
camera.position.set(0, 0, 5);
camera.lookAt(scene.position);
scene.add(camera);
// circle
const geo = new THREE.CircleBufferGeometry(10, 32);
const mat = new THREE.MeshBasicMaterial({
color: 0xaaff55
});
const mesh = new THREE.Mesh(geo, mat);
// outline
const geo2 = geo.clone();
// strip the center point from CircleBufferGeometry:
geo2.attributes.position.array = geo.attributes.position.array.slice(3);
geo2.attributes.position.count--;
geo2.attributes.normal.array = geo.attributes.position.array.slice(3);
geo2.attributes.normal.count--;
geo2.deleteAttribute("uv"); // lines don't need UVs
geo2.index = null; // don't need indexing for this kind of line
const mat2 = new THREE.LineBasicMaterial({
color: 0x55aa00
});
const line = new THREE.LineLoop(geo2, mat2);
// data point
const group = new THREE.Group();
group.add(mesh);
group.add(line);
scene.add(group);
group.userData.scalingMatrix = new THREE.Matrix4().makeScale(2, 2, 2); // for storing the scaling matrix
function render() {
renderer.render(scene, camera);
}
function resize() {
W = window.innerWidth;
H = window.innerHeight;
aspect = W / H;
renderer.setSize(W, H);
camera.left = frustumSize * aspect / -2;
camera.right = frustumSize * aspect / 2;
camera.top = frustumSize / 2;
camera.bottom = frustumSize / -2;
camera.updateProjectionMatrix();
render();
}
window.addEventListener("resize", resize);
resize();
render();
// pretend this is reacting to your mouseovers
let over = true;
const resetMatrix = new THREE.Matrix4()
setInterval(() => {
group.applyMatrix4((over) ? group.userData.scalingMatrix : resetMatrix.getInverse(group.userData.scalingMatrix));
over = !over;
render();
}, 2000);
html,
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background: white;
}
<script src="https://threejs.org/build/three.js"></script>
There is also (and this is outside my sphere of knowledge) the SVGRenderer, which can render shapes as SVG objects. This may be easier to work with, again depending on your data. See a demo here: https://threejs.org/examples/#svg_sandbox
You can't use PointsMaterial to achieve the intended affect. You need a custom shader material for this or you try to enhance PointsMaterial with Material.onBeforeCompile(). I suggest you study the implementation of the following official example to check out the former approach:
https://threejs.org/examples/webgl_custom_attributes_points
An important part of the code is the vertex shader, which enables points of different sizes:
attribute float size;
attribute vec3 customColor;
varying vec3 vColor;
void main() {
vColor = customColor;
vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
gl_PointSize = size * ( 300.0 / -mvPosition.z );
gl_Position = projectionMatrix * mvPosition;
}
Notice the additional size attribute.
How would I go about adding a 2d sprite under a 3d object on top of a plane to show that my object(s) are highlighted/selected?
Also, how would I do this on uneven terrain?
I'm attaching a sample image to better explain my question:
I'm not sure that using a sprite is a good idea.
As you said about uneven terrain, it's better to use something not flat. For example, a sphere or a cylinder with a material with .alphaMap.
We need something ring-like for our effect of selection.
Let's suppose, you chose sphere, then you can set its material's alphaMap from a file or a dynamically created texture from canvas:
// alpha texture
var canvas = document.createElement("canvas");
canvas.width = 128;
canvas.height = 128;
var ctx = canvas.getContext("2d");
var gradient = ctx.createLinearGradient(0, 0, 0, 128);
gradient.addColorStop(0.35, "black");
gradient.addColorStop(0.475, "white");
gradient.addColorStop(0.525, "white");
gradient.addColorStop(0.65, "black");
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 128, 128);
var alphaTexture = new THREE.Texture(canvas);
alphaTexture.needsUpdate = true;
About .alphaMap:
The alpha map is a grayscale texture that controls the opacity across the surface (black: fully transparent; white: fully opaque). Default is null.
Only the color of the texture is used, ignoring the alpha channel if one exists. For RGB and RGBA textures, the WebGL renderer will use the green channel when sampling this texture due to the extra bit of precision provided for green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and luminance/alpha textures will also still work as expected.
That's why we have there black and white only.
Let's create an object of the simpliest NPC with a yellow ring which indicating that our NPC is selected:
var npc = function() {
var geom = new THREE.SphereGeometry(4, 4, 2);
geom.translate(0, 4, 0);
var mesh = new THREE.Mesh(geom, new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff
}));
// highlighter
geom.computeBoundingSphere();
var sphereGeom = new THREE.SphereGeometry(geom.boundingSphere.radius, 32, 24);
var sphereMat = new THREE.MeshBasicMaterial({
color: "yellow", // yellow ring
transparent: true, // to make our alphaMap work, we have to set this parameter to `true`
alphaMap: alphaTexture
});
var sphere = new THREE.Mesh(sphereGeom, sphereMat);
sphere.visible = false;
mesh.add(sphere);
mesh.userData.direction = new THREE.Vector3(Math.random() - 0.5, 0, Math.random() - 0.5).normalize();
mesh.userData.speed = Math.random() * 5 + 5;
mesh.position.set(
Math.random() * (worldWidth - 10) - (worldWidth - 10) * 0.5,
10,
Math.random() * (worldDepth - 10) - (worldDepth - 10) * 0.5
);
scene.add(mesh);
return mesh;
}
The rest is not so difficult.
We need an array of our NPCs which we'll check for intersection
var npcs = [];
for (var i = 0; i < 10; i++) {
npcs.push(npc());
}
and then on mousedown event we'll select our an NPC (taken from the interactive cubes example and modified for our needs):
window.addEventListener('mousedown', onMouseDown, false);
function onMouseDown(event) {
if (event.button != 2) return;
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
selector.setFromCamera( mouse, camera );
var intersects = selector.intersectObjects( npcs );
if ( intersects.length > 0 ) {
if ( INTERSECTED != intersects[ 0 ].object ) {
if ( INTERSECTED ) INTERSECTED.children[0].visible = INTERSECTED.selected;
INTERSECTED = intersects[ 0 ].object;
INTERSECTED.selected = INTERSECTED.children[0].visible;
INTERSECTED.children[0].visible = true;
}
} else {
if ( INTERSECTED ) INTERSECTED.children[0].visible = INTERSECTED.selected;
INTERSECTED = null;
}
}
jsfiddle example. Here you can select objects with the right mouse button.
I'm trying to recreate a functionality of a snippet I found that rotates any given element around another element, very much like a planetary rotation.
I want to use this 2D rotation system for a 3D project with three.js.
The 2D functionality is in this fiddle.
I'm having trouble implementing it in my own scene though. For starters, getting the elements' id's for meshes in three.js is tricky.
myObject.name = "objectName";
...
var object = scene.getObjectByName( "objectName" );
This example code where you name your meshes and pass it through isn't doing it for me - which marks the only search result I could find.
Also, I don't understand that by adding the third dimension how the trigonometric function would need to be written.
return {
x: Math.cos(ang) * r - Math.sin(ang) * r + coorX,
y: Math.sin(ang) * r + Math.cos(ang) * r + coorY
};
So there's some work involved that's simply beyond me. That's why I don't see how the functionality needs to look like for a 3D environment? Thank you if you can help me out with that.
I don't know why you had trouble implementing it because you could almost copy paste from the snippet you found.
I don't know how to use JSFiddle. I clicked update but I have no idea if you can see the changes. That's why I'll post them here as well.
//creates the scene
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(50, 500 / 400, 0.1, 1000);
var renderer = new THREE.WebGLRenderer();
renderer.setClearColorHex( 0xffffff, 1 );
renderer.setSize(625, 500);
document.body.appendChild(renderer.domElement);
//creates the two objects
var geometry = new THREE.SphereGeometry(1.5, 50, 50);
var material = new THREE.MeshPhongMaterial( {color: 0x9a806e} );
var sphere1 = new THREE.Mesh(geometry, material);
var sphere2 = new THREE.Mesh(geometry, material);
//sets positions
sphere1.position.set(0, 0, 0);
sphere2.position.set(-10, 0, 0);
//adds the two spheres to the scene
scene.add(sphere1);
scene.add(sphere2);
//default camera pos
camera.position.z = 400;
var object = {speed: 1.2, spin: 100, side: 0.0};
// copied from snippet
function rotation(coorX, coorY, object) {
object.side += (1.0 / object.speed);
var ang = object.side * 2.0 * Math.PI / 180.0;
var r = object.spin;
return {
x: Math.cos(ang) * r - Math.sin(ang) * r + coorX,
y: Math.sin(ang) * r + Math.cos(ang) * r + coorY
};
}
//renders the scene
var render = function () {
newpos = rotation(sphere1.position.x, sphere1.position.y, object);
sphere2.position.x = newpos.x;
sphere2.position.y = newpos.y;
renderer.render(scene, camera);
};
setInterval(render, 1000 / 60)
I also think you can use this in 3d as well. Because 3d only adds the z-axis and I think that makes no difference. But maybe you can explain me your concerns about using it in 3d.
Here's an alternative approach that lets three.js handle trigonometry for you: http://jsfiddle.net/SF9tX/438/
relevant code:
var sphere2Rig = new THREE.Object3D();
sphere2.position.x = -10;
sphere2Rig.add(sphere2);
scene.add(sphere2Rig);
var render = function () {
sphere2Rig.rotation.z -= 0.025;
renderer.render(scene, camera);
requestAnimationFrame(render);
};
So you create a "rig" that is positioned at the origin and contains the sphere2-object positioned somewhere else. Now if you rotate the rig, you automatically also rotate all children with it, using the rig's origin as center-point for rotation.
I made a rounded corner plane by merging circle and plane geometries.
The rendered version with a flat color works well, but the textured gets chopped up.
http://jsfiddle.net/28usyw12/
I suspect I have to somehow add some hinting or define how the texture should be rendered, but I don't really know how.
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 90, 1, 0.1, 1000 );
WIDTH = HEIGHT = 500
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setClearColor( 0xffffff );
renderer.setSize(WIDTH, HEIGHT);
light = new THREE.PointLight(0xffffff);
light.position.set(0,0,100);
scene.add(light);
# 628 × 697
camera.position.z = 5;
document.body.appendChild(renderer.domElement);
meshA = new THREE.Mesh()
makeRoundedCornerPlane = (offset=2, radius=2, smooth=16) ->
geometry = new THREE.Geometry()
offset = (offset - radius) / 2
radius = radius / 4
smooth = 16
cornerA = new THREE.CircleGeometry(radius, smooth, (Math.PI * 2 / 4) * 1, Math.PI * 2 / 4);
matrixA = new THREE.Matrix4();
matrixA.makeTranslation(0-offset, 0+offset, 0)
geometry.merge(cornerA, matrixA)
cornerB = new THREE.CircleGeometry(radius, smooth, (Math.PI * 2 / 4) * 0, Math.PI * 2 / 4);
matrixB = new THREE.Matrix4();
matrixB.makeTranslation(0+offset, 0+offset, 0)
geometry.merge(cornerB, matrixB)
cornerC = new THREE.CircleGeometry(radius, smooth, (Math.PI * 2 / 4) * 3, Math.PI * 2 / 4);
matrixC = new THREE.Matrix4();
matrixC.makeTranslation(0+offset, 0-offset, 0)
geometry.merge(cornerC, matrixC)
cornerD = new THREE.CircleGeometry(radius, smooth, (Math.PI * 2 / 4) * 2, Math.PI * 2 / 4);
matrixD = new THREE.Matrix4();
matrixD.makeTranslation(0-offset, 0-offset, 0)
geometry.merge(cornerD, matrixD)
planeA = new THREE.PlaneGeometry((offset+radius) * 2, offset * 2)
geometry.merge(planeA)
planeB = new THREE.PlaneGeometry(offset * 2, (offset+radius) * 2)
geometry.merge(planeB)
return geometry
meshA.geometry = makeRoundedCornerPlane(2, 0.5)
meshA.material = new THREE.MeshBasicMaterial
side:THREE.DoubleSide
color: new THREE.Color("rgb(255,0,0)")
#wireframe: true
meshB = new THREE.Mesh()
meshB.geometry = makeRoundedCornerPlane(2, 0.5)
meshB.material = new THREE.MeshBasicMaterial
side:THREE.DoubleSide
color: new THREE.Color("rgb(255,0,0)")
#wireframe: true
texture = new THREE.ImageUtils.loadTexture("/img/initializing.png");
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
meshB.material.map = texture
meshB.material.color = new THREE.Color(0xffffff)
meshB.position.x = -1
meshB.position.y = -1
scene.add(meshA)
scene.add(meshB)
update = ->
# meshA.scale.x += 0.001
# meshA.scale.y += 0.001
meshA.rotation.z += 0.002
meshA.rotation.y += 0.002
meshB.rotation.z += 0.002
meshB.rotation.y += 0.002
render = ->
renderer.render(scene, camera)
tick = ->
window.requestAnimationFrame(tick)
update()
render()
tick()
You'll need to fix the UV coordinates of each face in the mesh. UV Coordinates are what tells GL how to map the image onto the face. The top left corner should have UV Coordinate (0,0) and the bottom right should have (1,1).
One solution is to just iterate through each face and give it the normalized UV coordinates based on it's position in space, which could work for a simple example like yours. You basically calculate the bounding box of your shape to normalize the vertex coordinates, then create 3 UV Coordinates for each face (since each face is a triangle).
Try this: http://jsfiddle.net/MacroMeez/e84y9bbq/1/
remapUVs = (geo) ->
geo.computeBoundingBox()
min = geo.boundingBox.min
max = geo.boundingBox.max
offset = new THREE.Vector2(0 - min.x, 0 - min.y)
size = new THREE.Vector2(max.x - min.x, max.y - min.y)
# Remove the old UVs that were incorrect
geo.faceVertexUvs[0] = []
for face, i in geo.faces
v1 = geo.vertices[face.a]
v2 = geo.vertices[face.b]
v3 = geo.vertices[face.c]
# Push on a new UV based on its position inside the shape
geo.faceVertexUvs[0].push [
new THREE.Vector2((v1.x + offset.x)/size.x, (v1.y + offset.y)/size.y),
new THREE.Vector2((v2.x + offset.x)/size.x, (v2.y + offset.y)/size.y),
new THREE.Vector2((v3.x + offset.x)/size.x, (v3.y + offset.y)/size.y)
]
geo.uvsNeedUpdate = true
based off the code found THREE.js generate UV coordinate
If you're familiar at all with blender or other 3d software, you can also create and map the mesh there then import it and not deal with this.
I am trying to input some known acr values from another program and reproduce them in three.js
Right now I am using the following code I found on this site. It draw the arc fine, although it may not be the best option.
function DRAWarc(){
// smooth my curve over this many points
var numPoints = 100;
spline = new THREE.SplineCurve3([
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 200, 0),
new THREE.Vector3(150, 150, 0)
]);
var material = new THREE.LineBasicMaterial({
color: 0xff00f0,
});
var geometry = new THREE.Geometry();
var splinePoints = spline.getPoints(numPoints);
for(var i = 0; i < splinePoints.length; i++){
geometry.vertices.push(splinePoints[i]);
}
var line = new THREE.Line(geometry, material);
scene.add(line);
}
The following are the known variables.
Center point (X,Y) (if the are was a complete circle, the center of the circle)
radius (if it were a circle)
start angle (I'm not positive, but I think this is the degree, if it were a circle, going counter-clockwise, with 0 being to the right of the circle)
end angle (see above)
more code!
///////////
// SCENE //
///////////
scene = new THREE.Scene();
////////////
// CAMERA //
////////////
var SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
viewsize = 900;
camera = new THREE.OrthographicCamera(
SCREEN_WIDTH / - 2, SCREEN_WIDTH / 2,
SCREEN_HEIGHT / 2, SCREEN_HEIGHT / - 2,
1, 1e6 );
camera.position.z = 2000;
scene.add(camera);
camera.lookAt(new THREE.Vector3(2100, 3600, 0));
//////////////
// RENDERER //
//////////////
// create and start the renderer
if ( Detector.webgl ){
renderer = new THREE.WebGLRenderer();
//alert('no problem.');
}else{
renderer = new THREE.CanvasRenderer();
alert('problem.');
}
renderer.setClearColor("white", 1);
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
container = document.body;
container.appendChild( renderer.domElement );
////////////
// EVENTS //
////////////
// automatically resize renderer
THREEx.WindowResize(renderer, camera);
// toggle full-screen on given key press
THREEx.FullScreen.bindKey({ charCode : 'm'.charCodeAt(0) });
///////////
// STATS //
///////////
// displays current and past frames per second attained by scene
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.bottom = '0px';
stats.domElement.style.zIndex = 100;
container.appendChild( stats.domElement );
///////////
// LIGHT //
///////////
// create a light
var light = new THREE.PointLight(0xffffff);
light.position.set(0,250,0);
scene.add(light);
var ambientLight = new THREE.AmbientLight(0x111111);
// scene.add(ambientLight);
//////////////
// GEOMETRY //
//////////////
// most objects displayed are a "mesh":
// a collection of points ("geometry") and
// a set of surface parameters ("material")
doWork();
}
function animate()
{
requestAnimationFrame( animate );
render();
update();
}
function update()
{
// delta = change in time since last call (in seconds)
var delta = clock.getDelta();
// functionality provided by THREEx.KeyboardState.js
if ( keyboard.pressed("1") )
document.getElementById('message').innerHTML = ' Have a nice day! - 1';
if ( keyboard.pressed("2") )
document.getElementById('message').innerHTML = ' Have a nice day! - 2 ';
//controls.update();
stats.update();
}
function render()
{
renderer.render( scene, camera );
}`
You can draw arc with the circle geometry
// compute angle between p1 and p2
var angle = Math.acos(p1.dot(p2)/(p1.length()*p2.length()));
// create arc
var geometry = new THREE.CircleGeometry(radius, nbSegments, 0, angle);
// remove center vertex
geometry.vertices.splice(0,1);
// TODO: move the arc to the good place in the scene
// add arc to the scene
scene.add(new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xff00f0 }));
So after a little research I found the following post.
How do I calculate a point on a circle’s circumference?
which led me to this bit of math that can be adapted to any language:
x = cx + r * cos(a)
y = cy + r * sin(a)
Where r is the radius, cx,cy the origin, and a the angle from 0..2PI radians or 0..360 degrees.
and heres some fun reading material!
http://en.wikipedia.org/wiki/Circle#Equations
EDIT: just completed the rough draft for this project. enjoi!
i does not draw a spline, instead it draws a line with 102 points. the start of the arc, the end, and 100 evenly spaced points in between. it works well, and i will add a variable to the number of lines to reduce memory if needed.
function getARC(x, y, r, a){
a = a * (Math.PI/180);
var ax = +x + +r * Math.cos(+a),
ay = +y + +r * Math.sin(+a),
res = [];
res['x'] = ax,
res['y'] = ay;
return res;
}
function DRAWarc(cx, cy, ra, sa, ea){
var cx = '2473.5737';
var cy = '3145.1300';
var ra = '47.5538';
var sa = '2';
var ea = '91';
var material = new THREE.LineBasicMaterial({
color: 0xff00f0,
});
var geometry = new THREE.Geometry();
var s = getARC(cx, cy, ra, sa);
geometry.vertices.push(new THREE.Vector3(s['x'], s['y'], 0));
var step = (ea - sa)/100;
for(var i=1;i<=100;i++){
var t = getARC(cx, cy, ra, (+sa + (+step * +i)));
geometry.vertices.push(new THREE.Vector3(t['x'], t['y'], 0));
//alert((+sa + (+step * +i)));
}
var f = getARC(cx, cy, ra, ea);
geometry.vertices.push(new THREE.Vector3(f['x'], f['y'], 0));
var line = new THREE.Line(geometry, material);
scene.add(line);
}