Related
Learning Three Js by my own and with some (incredible) stuff found on Internet, I use Script to generate a LowPoly Planet, but trees does not cast Shadow on the ground.
I look for help but didn't find something to work. Can you explain me why please ?
I tried many things with MeshLambertMaterial and things like :
mesh.castShadow = true; mesh.receiveShadow = true; etc..
Here is a fiddle.
https://jsfiddle.net/Fliip36/fcz2psuj/
Trees are objects, that consist of several meshes (as they are Object3D or Group), so to make them to cast shadows, you need to use .traverse() method.
So change this part
objects.push(obj);
obj.scale.set(0.01, 0.01, 0.01);
obj.tween = TweenMax.to(obj.scale, rnd(3, 10), { x: 1, y: 1, z: 1, ease: Elastic.easeOut.config(1, 0.2), delay: rnd(0, 4) });
obj.receiveShadow = true;
obj.castShadow = true;
planet.add(obj);
to this:
objects.push(obj);
obj.scale.set(0.01, 0.01, 0.01);
obj.tween = TweenMax.to(obj.scale, rnd(3, 10), { x: 1, y: 1, z: 1, ease: Elastic.easeOut.config(1, 0.2), delay: rnd(0, 4) });
obj.traverse(o => {
if (o.isMesh){ // set these parameters for meshes only
o.receiveShadow = true;
o.castShadow = true;
}
})
planet.add(obj);
I have a simple object that draws a 3d gizmo at 0, 0, 0. If the camera is centered on 0, 0, 0, then it draws the gizmo at the center of the screen.
I would like to "lift" this gizmo and render it at the bottom right of the screen in screen coordinates, without rotating it. Basically, I want the gizmo to show the rotation of the center of the screen without blocking the view and without having to focus on a specific point. So I want to do away with the model matrix, or something.
I got the following to work by translating the projection matrix:
this.gl.uniformMatrix4fv(this.modelMatrixUniform, false, modelMatrix);
this.gl.uniformMatrix4fv(this.viewMatrixUniform, false, viewMatrix);
const bottomRightMat = mat4.create();
mat4.translate(bottomRightMat, projectionMatrix, [5, -3, 0]);
this.gl.uniformMatrix4fv(this.projectionMatrixUniform, false, bottomRightMat);
this.gl.drawElements(this.gl.LINES, this.indexBuffer.getLength(), this.gl.UNSIGNED_SHORT, 0);
But the gizmo has been rotated into its new position. The red line should still point down and to the left, since that's the direction of the positive X axis at the center of the screen. Also, the numbers 5 and 3 are arbitrary, and I don't think they would work at different zooms or camera locations.
Is there a way to specify a matrix transform that takes the center of the screen and translates it in screen space?
One way would be to change the viewport when rendering that object.
// size of area in bottom right
const miniWidth = 150;
const miniHeight = 100;
gl.viewport(gl.canvas.width - miniWidth, gl.canvas.height - miniHeight, miniWidth, miniHeight);
// now draw. you'll need to zoom in, like set the camera closer
// or move the object closer or add a scale matrix after the projection
// matrix as in projection * scale * view * ...
you'll need a projection matrix that matches the aspect ratio of the new viewport and you'll either need to scale the object, put the camera closer, or add a 2D scale between the projection and view matrices.
Remember to put the viewport back to the full canvas to render the rest of the scene.
const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
void main() {
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(vec3(0), 1);
}
`
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
],
indices: {
numComponents: 2,
data: [
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7,
],
},
});
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const fov = 90 * Math.PI / 180;
const zNear = 0.5;
const zFar = 100;
const projection = mat4.perspective(mat4.create(),
fov, gl.canvas.clientWidth / gl.canvas.clientHeight, zNear, zFar);
const eye = [0, 0, 10];
const target = [0, 0, 0];
const up = [0, 1, 0];
const view = mat4.lookAt(mat4.create(), eye, target, up);
drawCube([-8, 0, 0], projection, view);
drawCube([-4, 0, 0], projection, view);
drawCube([ 0, 0, 0], projection, view);
drawCube([ 4, 0, 0], projection, view);
drawCube([ 8, 0, 0], projection, view);
const iconAreaWidth = 100;
const iconAreaHeight = 75;
gl.viewport(
gl.canvas.width - iconAreaWidth, 0,
iconAreaWidth, iconAreaHeight);
const iconProjection = mat4.perspective(mat4.create(),
fov, iconAreaWidth / iconAreaHeight, zNear, zFar);
// compute the zoom size need to make things the sngs
const scale = gl.canvas.clientHeight / iconAreaHeight;
mat4.scale(iconProjection, iconProjection, [scale, scale, 1]);
drawCube([ 0, 0, 0], iconProjection, view);
function drawCube(translation, projection, view) {
const viewProjection = mat4.multiply(mat4.create(), projection, view);
const world = mat4.multiply(
mat4.create(),
mat4.fromTranslation(mat4.create(), translation),
mat4.fromRotation(mat4.create(), time, [0.42, 0.56, 0.70]));
const worldViewProjection = mat4.multiply(mat4.create(), viewProjection, world);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: worldViewProjection,
});
twgl.drawBufferInfo(gl, bufferInfo, gl.LINES);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; background: #CDE; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix.js"></script>
Another is to compute an off center frustum projection matrix. Instead of mat4.perspective use mat4.frustum
function perspectiveWithCenter(
fieldOfView, width, height, near, far, centerX = 0, centerY = 0) {
const aspect = width / height;
// compute the top and bottom of the near plane of the view frustum
const top = Math.tan(fieldOfView * 0.5) * near;
const bottom = -top;
// compute the left and right of the near plane of the view frustum
const left = aspect * bottom;
const right = aspect * top;
// compute width and height of the near plane of the view frustum
const nearWidth = right - left;
const nearHeight = top - bottom;
// convert the offset from canvas units to near plane units
const offX = centerX * nearWidth / width;
const offY = centerY * nearHeight / height;
const m = mat4.create();
mat4.frustum(
m,
left + offX,
right + offX,
bottom + offY,
top + offY,
near,
far);
return m;
}
So to draw your gizmo set call something like
const gizmoCenterX = -gl.canvas.clientWidth / 2 + 50;
const gizmoCenterY = gl.canvas.clientHeight / 2 - 50;
const offsetProjection = perspectiveWithCenter(
fov, gl.canvas.clientWidth, gl.canvas.clientHeight, zNear, zFar,
gizmoCenterX, gizmoCenterY);
const vs = `
attribute vec4 position;
uniform mat4 u_worldViewProjection;
void main() {
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
void main() {
gl_FragColor = vec4(vec3(0), 1);
}
`
const gl = document.querySelector("canvas").getContext("webgl");
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const bufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: [
-1, -1, -1,
1, -1, -1,
1, 1, -1,
-1, 1, -1,
-1, -1, 1,
1, -1, 1,
1, 1, 1,
-1, 1, 1,
],
indices: {
numComponents: 2,
data: [
0, 1,
1, 2,
2, 3,
3, 0,
4, 5,
5, 6,
6, 7,
7, 4,
0, 4,
1, 5,
2, 6,
3, 7,
],
},
});
function perspectiveWithCenter(
fieldOfView, width, height, near, far, centerX = 0, centerY = 0) {
const aspect = width / height;
// compute the top and bottom of the near plane of the view frustum
const top = Math.tan(fieldOfView * 0.5) * near;
const bottom = -top;
// compute the left and right of the near plane of the view frustum
const left = aspect * bottom;
const right = aspect * top;
// compute width and height of the near plane of the view frustum
const nearWidth = right - left;
const nearHeight = top - bottom;
// convert the offset from canvas units to near plane units
const offX = centerX * nearWidth / width;
const offY = centerY * nearHeight / height;
const m = mat4.create();
mat4.frustum(
m,
left + offX,
right + offX,
bottom + offY,
top + offY,
near,
far);
return m;
}
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const fov = 90 * Math.PI / 180;
const zNear = 0.5;
const zFar = 100;
const projection = perspectiveWithCenter(
fov, gl.canvas.clientWidth, gl.canvas.clientHeight, zNear, zFar);
const eye = [0, 0, 10];
const target = [0, 0, 0];
const up = [0, 1, 0];
const view = mat4.lookAt(mat4.create(), eye, target, up);
drawCube([-8, 0, 0], projection, view);
drawCube([-4, 0, 0], projection, view);
drawCube([ 0, 0, 0], projection, view);
drawCube([ 4, 0, 0], projection, view);
drawCube([ 8, 0, 0], projection, view);
const gizmoCenterX = -gl.canvas.clientWidth / 2 + 50;
const gizmoCenterY = gl.canvas.clientHeight / 2 - 50;
const offsetProjection = perspectiveWithCenter(
fov, gl.canvas.clientWidth, gl.canvas.clientHeight, zNear, zFar,
gizmoCenterX, gizmoCenterY);
drawCube([ 0, 0, 0], offsetProjection, view);
function drawCube(translation, projection, view) {
const viewProjection = mat4.multiply(mat4.create(), projection, view);
const world = mat4.multiply(
mat4.create(),
mat4.fromTranslation(mat4.create(), translation),
mat4.fromRotation(mat4.create(), time, [0.42, 0.56, 0.70]));
const worldViewProjection = mat4.multiply(mat4.create(), viewProjection, world);
gl.useProgram(programInfo.program);
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
twgl.setUniforms(programInfo, {
u_worldViewProjection: worldViewProjection,
});
twgl.drawBufferInfo(gl, bufferInfo, gl.LINES);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; background: #CDE; }
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.8.1/gl-matrix.js"></script>
I'm trying to work out how to warp all coordinates in a Three.js scene around a specific pivot point / axis. The best way to describe it is as if I was to place a tube somewhere in the scene and everything else in the scene would curve around that axis and keep the same distance from that axis.
If it helps, this diagram is what I'm trying to achieve. The top part is as if you were looking at the scene from the side and the bottom part is as if you were looking at it from a perspective. The red dot / line is where the pivot point is.
To further complicate matters, I'd like to stop the curve / warp from wrapping back on itself, so the curve stops when it's horizontal or vertical like the top-right example in the diagram.
Any insight into how to achieve this using GLSL shaders, ideally in Three.js but I'll try to translate if they can be described clearly otherwise?
I'm also open to alternative approaches to this as I'm unsure how best to describe what I'm after. Basically I want an inverted "curved world" effect where the scene is bending up and away from you.
First I'd do it in 2D just like your top diagram.
I have no idea if this is the correct way to do this or even a good way but, doing it in 2D seemed easier than 3D and besides the effect you want is actually a 2D. X is not changing at all, only Y, and Z so solving it in 2D seems like it would lead to solution.
Basically we choose a radius for a circle. At that radius for every unit of X past the circle's center we want to wrap one horizontal unit to one unit around the circle. Given the radius we know the distance around the circle is 2 * PI * radius so we can easily compute how far to rotate around our circle to get one unit. It's just 1 / circumference * Math.PI * 2 We do that for some specified distance past the circle's center
const m4 = twgl.m4;
const v3 = twgl.v3;
const ctx = document.querySelector('canvas').getContext('2d');
const gui = new dat.GUI();
resizeToDisplaySize(ctx.canvas);
const g = {
rotationPoint: {x: 100, y: ctx.canvas.height / 2 - 50},
radius: 50,
range: 60,
};
gui.add(g.rotationPoint, 'x', 0, ctx.canvas.width).onChange(render);
gui.add(g.rotationPoint, 'y', 0, ctx.canvas.height).onChange(render);
gui.add(g, 'radius', 1, 100).onChange(render);
gui.add(g, 'range', 0, 300).onChange(render);
render();
window.addEventListener('resize', render);
function render() {
resizeToDisplaySize(ctx.canvas);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const start = g.rotationPoint.x;
const curveAmount = g.range / g.radius;
const y = ctx.canvas.height / 2;
drawDot(ctx, g.rotationPoint.x, g.rotationPoint.y, 'red');
ctx.beginPath();
ctx.arc(g.rotationPoint.x, g.rotationPoint.y, g.radius, 0, Math.PI * 2, false);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.fillStyle = 'black';
const invRange = g.range > 0 ? 1 / g.range : 0; // so we don't divide by 0
for (let x = 0; x < ctx.canvas.width; x += 5) {
for (let yy = 0; yy <= 30; yy += 10) {
const sign = Math.sign(g.rotationPoint.y - y);
const amountToApplyCurve = clamp((x - start) * invRange, 0, 1);
let mat = m4.identity();
mat = m4.translate(mat, [g.rotationPoint.x, g.rotationPoint.y, 0]);
mat = m4.rotateZ(mat, curveAmount * amountToApplyCurve * sign);
mat = m4.translate(mat, [-g.rotationPoint.x, -g.rotationPoint.y, 0]);
const origP = [x, y + yy, 0];
origP[0] += -g.range * amountToApplyCurve;
const newP = m4.transformPoint(mat, origP);
drawDot(ctx, newP[0], newP[1], 'black');
}
}
}
function drawDot(ctx, x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x - 1, y - 1, 3, 3);
}
function clamp(v, min, max) {
return Math.min(max, Math.max(v, min));
}
function resizeToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<!-- using twgl just for its math library -->
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
Notice the only place that matches perfectly is when the radius touches a line of points. Inside the radius things will get pinched, outside they'll get stretched.
Putting that in a shader in the Z direction for actual use
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('canvas'),
});
const gui = new dat.GUI();
const scene = new THREE.Scene();
const fov = 75;
const aspect = 2; // the canvas default
const zNear = 1;
const zFar = 1000;
const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
function lookSide() {
camera.position.set(-170, 35, 210);
camera.lookAt(0, 25, 210);
}
function lookIn() {
camera.position.set(0, 35, -50);
camera.lookAt(0, 25, 0);
}
{
scene.add(new THREE.HemisphereLight(0xaaaaaa, 0x444444, .5));
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-1, 20, 4 - 15);
scene.add(light);
}
const point = function() {
const material = new THREE.MeshPhongMaterial({
color: 'red',
emissive: 'hsl(0,50%,25%)',
wireframe: true,
});
const radiusTop = 1;
const radiusBottom = 1;
const height = 0.001;
const radialSegments = 32;
const geo = new THREE.CylinderBufferGeometry(
radiusTop, radiusBottom, height, radialSegments);
const sphere = new THREE.Mesh(geo, material);
sphere.rotation.z = Math.PI * .5;
const mesh = new THREE.Object3D();
mesh.add(sphere);
scene.add(mesh);
mesh.position.y = 88;
mesh.position.z = 200;
return {
point: mesh,
rep: sphere,
};
}();
const vs = `
// -------------------------------------- [ VS ] ---
#define PI radians(180.0)
uniform mat4 center;
uniform mat4 invCenter;
uniform float range;
uniform float radius;
varying vec3 vNormal;
mat4 rotZ(float angleInRadians) {
float s = sin(angleInRadians);
float c = cos(angleInRadians);
return mat4(
c,-s, 0, 0,
s, c, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
}
mat4 rotX(float angleInRadians) {
float s = sin(angleInRadians);
float c = cos(angleInRadians);
return mat4(
1, 0, 0, 0,
0, c, s, 0,
0, -s, c, 0,
0, 0, 0, 1);
}
void main() {
float curveAmount = range / radius;
float invRange = range > 0.0 ? 1.0 / range : 0.0;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
vec4 point = invCenter * mvPosition;
float amountToApplyCurve = clamp(point.z * invRange, 0.0, 1.0);
float s = sign(point.y);
mat4 mat = rotX(curveAmount * amountToApplyCurve * s);
point = center * mat * (point + vec4(0, 0, -range * amountToApplyCurve, 0));
vNormal = mat3(mat) * normalMatrix * normal;
gl_Position = projectionMatrix * point;
}
`;
const fs = `
// -------------------------------------- [ FS ] ---
varying vec3 vNormal;
uniform vec3 color;
void main() {
vec3 light = vec3( 0.5, 2.2, 1.0 );
light = normalize( light );
float dProd = dot( vNormal, light ) * 0.5 + 0.5;
gl_FragColor = vec4( vec3( dProd ) * vec3( color ), 1.0 );
}
`;
const centerUniforms = {
radius: { value: 0 },
range: { value: 0 },
center: { value: new THREE.Matrix4() },
invCenter: { value: new THREE.Matrix4() },
};
function addUniforms(uniforms) {
return Object.assign(uniforms, centerUniforms);
}
{
const uniforms = addUniforms({
color: { value: new THREE.Color('hsl(100,50%,50%)') },
});
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
});
const planeGeo = new THREE.PlaneBufferGeometry(1000, 1000, 100, 100);
const mesh = new THREE.Mesh(planeGeo, material);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}
{
const uniforms = addUniforms({
color: { value: new THREE.Color('hsl(180,50%,50%)' ) },
});
const material = new THREE.ShaderMaterial( {
uniforms: uniforms,
vertexShader: vs,
fragmentShader: fs,
});
const boxGeo = new THREE.BoxBufferGeometry(10, 10, 10, 20, 20, 20);
for (let x = -41; x <= 41; x += 2) {
for (let z = 0; z <= 40; z += 2) {
const base = new THREE.Object3D();
const mesh = new THREE.Mesh(boxGeo, material);
mesh.position.set(0, 5, 0);
base.position.set(x * 10, 0, z * 10);
base.scale.y = 1 + Math.random() * 2;
base.add(mesh);
scene.add(base);
}
}
}
const g = {
radius: 59,
range: 60,
side: true,
};
class DegRadHelper {
constructor(obj, prop) {
this.obj = obj;
this.prop = prop;
}
get v() {
return THREE.Math.radToDeg(this.obj[this.prop]);
}
set v(v) {
this.obj[this.prop] = THREE.Math.degToRad(v);
}
}
gui.add(point.point.position, 'z', -300, 300).onChange(render);
gui.add(point.point.position, 'y', -150, 300).onChange(render);
gui.add(g, 'radius', 1, 100).onChange(render);
gui.add(g, 'range', 0, 300).onChange(render);
gui.add(g, 'side').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'x'), 'v', -180, 180).name('rotX').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'y'), 'v', -180, 180).name('rotY').onChange(render);
gui.add(new DegRadHelper(point.point.rotation, 'z'), 'v', -180, 180).name('rotZ').onChange(render);
render();
window.addEventListener('resize', render);
function render() {
if (resizeToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
if (g.side) {
lookSide();
} else {
lookIn();
}
camera.updateMatrixWorld();
point.rep.scale.set(g.radius, g.radius, g.radius);
point.point.updateMatrixWorld();
centerUniforms.center.value.multiplyMatrices(
camera.matrixWorldInverse, point.point.matrixWorld);
centerUniforms.invCenter.value.getInverse(centerUniforms.center.value);
centerUniforms.range.value = g.range;
centerUniforms.radius.value = g.radius;
renderer.render(scene, camera);
}
function resizeToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needUpdate = canvas.width !== width || canvas.height !== height;
if (needUpdate) {
renderer.setSize(width, height, false);
}
return needUpdate;
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/95/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.2/dat.gui.min.js"></script>
Honestly I have a feeling there's an easier way I'm missing but for the moment it seems to kind of be working.
I'm playing with draw groups in THREE.TriangleStripDrawMode draw mode, and I'm seeing some odd artifacts. I think it might be a bug, but I'd like someone else to confirm that I'm not doing something wrong before submitting a report.
In the snippet below, I create 4 squares composed of 4 triangles each. They're all indexed for right-hand rendering. (I realize that triangle strips don't technically need indexing, but I could potentially have complex shapes with re-used vertices.) Their other properties and results are as follows:
Red Square
Two groups: 0-9 and 9-12
Standard front-side rendering
Black artifact on the back-face of triangle index 1
Green Square
One group: 0-12
Standard front-side rendering
Black artifacts on the back-face of triangles index 1 & 3
The same thing happens when I don't use grouping at all
Blue Square
Two groups: 0-9 and 9-12
Double-side rendering
Renders as expected
Yellow Square
One group: 0-12
Double-side rendering
Triangle index 3 renders black on both sides
So did I miss something, or should I submit this as a bug in three.js?
var renderer, scene, camera, controls, stats;
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight,
FOV = 35,
NEAR = 1,
FAR = 1000;
function createShapes(){
var bg = new THREE.BufferGeometry();
bg.addAttribute("position", new THREE.BufferAttribute(new Float32Array([
-1, 1, 0,
-1, -1, 0,
0, 1, 0,
0, -1, 0,
1, 1, 0,
1, -1, 0
]), 3));
bg.addAttribute("normal", new THREE.BufferAttribute(new Float32Array([
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1,
0, 0, 1
]), 3));
bg.setIndex(new THREE.BufferAttribute(new Uint32Array([
0, 1, 2,
3, 2, 1,
2, 3, 4,
5, 4, 3
]), 1));
var group1 = bg.clone(),
group2 = bg.clone(),
group3 = bg.clone(),
group4 = bg.clone();
/**/
group1.clearGroups();
group1.addGroup(0, 9, 0);
group1.addGroup(9, 3, 0);
group2.clearGroups();
group2.addGroup(0, 12, 0);
group3.clearGroups();
group3.addGroup(0, 9, 0);
group3.addGroup(9, 3, 0);
group4.clearGroups();
group4.addGroup(0, 12, 0);
/**/
var mat1 = new THREE.MeshPhongMaterial({color: "red"}),
mat2 = new THREE.MeshPhongMaterial({color: "green"}),
mat3 = new THREE.MeshPhongMaterial({color: "blue", side: THREE.DoubleSide}),
mat4 = new THREE.MeshPhongMaterial({color: "yellow", side: THREE.DoubleSide});
var m1 = new THREE.Mesh(group1, [mat1]),
m2 = new THREE.Mesh(group2, [mat2]),
m3 = new THREE.Mesh(group3, [mat3]),
m4 = new THREE.Mesh(group4, [mat4]);
m1.drawMode = THREE.TriangleStripDrawMode;
m2.drawMode = THREE.TriangleStripDrawMode;
m3.drawMode = THREE.TriangleStripDrawMode;
m4.drawMode = THREE.TriangleStripDrawMode;
m1.position.set(-2, 2, 0);
m2.position.set(2, 2, 0);
m3.position.set(-2, -2, 0);
m4.position.set(2, -2, 0);
scene.add(m1, m2, m3, m4);
}
function init() {
document.body.style.backgroundColor = "slateGray";
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
document.body.appendChild(renderer.domElement);
document.body.style.overflow = "hidden";
document.body.style.margin = "0";
document.body.style.padding = "0";
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
camera.position.z = 20;
scene.add(camera);
controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.dynamicDampingFactor = 0.5;
controls.rotateSpeed = 3;
var light = new THREE.PointLight(0xffffff, 1, Infinity);
camera.add(light);
stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.top = '0';
document.body.appendChild(stats.domElement);
resize();
window.onresize = resize;
// POPULATE EXAMPLE
createShapes();
animate();
}
function resize() {
WIDTH = window.innerWidth;
HEIGHT = window.innerHeight;
if (renderer && camera && controls) {
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
controls.handleResize();
}
}
function render() {
renderer.render(scene, camera);
}
function animate() {
requestAnimationFrame(animate);
render();
controls.update();
stats.update();
}
function threeReady() {
init();
}
(function () {
function addScript(url, callback) {
callback = callback || function () { };
var script = document.createElement("script");
script.addEventListener("load", callback);
script.setAttribute("src", url);
document.head.appendChild(script);
}
addScript("https://threejs.org/build/three.js", function () {
addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () {
addScript("https://threejs.org/examples/js/libs/stats.min.js", function () {
threeReady();
})
})
})
})();
r86 (the problem has actually been around for a while)
I had an "AHA!" moment while looking at some other examples. My indexing was off, which is also throwing off my grouping.
The indexing should be:
bg.setIndex(new THREE.BufferAttribute(new Uint32Array([
0, 1, 2, 3, 4, 5
]), 1));
Which makes sense, because that's how the triangle strip defines the steps of its vertices.
Then to draw a full square, I needed a single group:
group1.addGroup(0, 6, 0);
Which means start at the group index of 0, for 6 group indices (which covers all of them).
There's still a problem when trying to render an (odd index) individual triangle. Because the winding order for odd triangles is backwards, creating a group that starts with an odd triangle will not be lit correctly (renders black). But that's for another question...
Once a transformation of coordinates has taken place during a the rendering of a computer graphics scene, how do you map inputs on the rendered scene back to the original actor(s) coordinate systems?
Using this JSFiddle https://jsfiddle.net/bbz5s183/3/ as a starting point, implement the canvas click event handler so that.
It can identify if a star was clicked.
It will work consistently no matter how the canvas is resized.
JSFIDDLE SCRIPT CONTENT BELOW
var draggable = document.getElementById('draggable')
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
// Draw a star in a 1 x 1 coordinate plane.
function star(color) {
context.beginPath();
context.moveTo(0.5, 0);
context.lineTo(0.15, 1.0);
context.lineTo(1.0, 0.4);
context.lineTo(0, 0.4);
context.lineTo(0.85, 1.0);
context.closePath();
context.fillStyle = color;
context.fill();
}
// Draw a scene of stars in a coordinate plane defined by the canvas.
// This is initially 300 x 300, but can be resized to anything by dragging the gray border.
function render() {
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(canvas.width / 2, canvas.height / 2);
context.scale(canvas.width / 5, canvas.height / 5);
star('red');
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(canvas.width / 4, canvas.height / 4);
context.scale(canvas.width / 5, canvas.height / 5);
star('yellow');
}
// Pop an alert indicating which star (if any) was clicked on.
// NOTE: The logic MUST work consistently no matter how the canvas is resized.
canvas.addEventListener('click', function (event) {
event.stopImmediatePropagation();
// HELP ME !!!
// HELP ME !!!
// HELP ME !!!
// HELP ME !!!
});
// IGNORE: It allows the canvas to resized by dragging on it.
draggable.addEventListener('mousedown', function handleMouseDown(mousedown) {
document.addEventListener('mouseup', function handleMouseUp(mouseup) {
var currWidth = Number(canvas.getAttribute('width'));
var deltaWidth = mouseup.clientX - mousedown.clientX;
var currHeight = Number(canvas.getAttribute('height'));
var deltaHeight = mouseup.clientY - mousedown.clientY;
canvas.setAttribute('width', currWidth + deltaWidth);
canvas.setAttribute('height', currHeight + deltaHeight);
document.removeEventListener('mouseup', handleMouseUp);
render();
});
});
render();
Answered my own question: https://jsfiddle.net/bbz5s183/4/
JAVASCRIPT FOLLOWS
// Draw a scene of stars in a coordinate plane defined by the canvas.
// This is initially 300 x 300, but can be resized to anything by dragging the gray border.
function render() {
bounds = [];
/* RENDER RED ACTOR - BOUNDING BOX */
var red = {
name: 'red',
// Translate to 25% right, 25% down on canvas.
x: 0.25 * canvas.width,
y: 0.25 * canvas.height,
// Scale to fill 20% of canvas.
width: 0.2 * canvas.width,
height: 0.2 * canvas.height
};
context.setTransform(1, 0, 0, 1, 0, 0);
box('red', red);
bounds.push(red);
/* RENDER RED ACTOR - MODEL */
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(red.x, red.y);
context.scale(red.width, red.height);
star('red');
/* RENDER YELLOW ACTOR - BOUNDING BOX */
var yellow = {
name: 'yellow',
// Translate to 50% right, 50% down on canvas.
x: 0.50 * canvas.width,
y: 0.50 * canvas.height,
// Scale to fill 20% of canvas.
width: 0.2 * canvas.width,
height: 0.2 * canvas.height
};
context.setTransform(1, 0, 0, 1, 0, 0);
box('yellow', yellow);
bounds.push(yellow);
/* RENDER YELLOW ACTOR - MODEL */
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(yellow.x, yellow.y);
context.scale(yellow.width, yellow.height);
star('yellow');
}
// Pop an alert indicating which star (if any) was clicked on.
// NOTE: The logic MUST work consistently no matter how the canvas is resized.
canvas.addEventListener('click', function (event) {
var x = event.pageX - event.target.offsetLeft;
var y = event.pageY - event.target.offsetTop;
for (var i = 0; i < bounds.length; i++) {
if (boxIntersection(bounds[i], x, y)) {
alert(bounds[i].name);
};
}
event.stopImmediatePropagation();
return false;
});