Related
I'm using code from this repo - scribble, which is using three.js r87. I followed the Updating THREE.Geometry to THREE.BufferGeometry tutorial in order to upgrade the code to three.js r144. I've got one function correct, but the other one is giving me trouble.
The mousePressed function was easy to update:
function mousePressed() {
const point = new THREE.Vector3(mouseX, mouseY, 0);
// const geometry = new THREE.Geometry();
// geometry.vertices.push(point);
let points = [];
points.push(point);
let geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geometry, material);
scene.add(line);
selected = line;
}
But I updated mouseDragged(), and I don't understand why it's not working:
function mouseDragged() {
const line = selected;
const point = new THREE.Vector3(mouseX, mouseY, 0);
const oldgeometry = line.geometry;
// const newgeometry = new THREE.Geometry();
// newgeometry.vertices = oldgeometry.vertices;
// newgeometry.vertices.push(point);
let newgeometry = new THREE.BufferGeometry();
let positions = oldgeometry.attributes.position.array;
for (let i = 0; i < positions.length; i += 3) {
const v = new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]);
positions[i] = v.x;
positions[i + 1] = v.y;
positions[i + 2] = v.z;
}
positions.push(point); // I should just be able to add the new point, no?
newgeometry.attributes.position.needsUpdate = true;
line.geometry = newgeometry;
scene.add(line); // I re-added the line, just in case. But the line does not show up, whether or not I do this.
selected = line;
}
Help is much appreciated!
Thanks
The code from the resource is highly inefficient since you continuously allocate geometries all the time without disposal management which will lead to a memory leak. Try the following approach:
let camera, scene, renderer, line;
const frustumSize = 4;
let index = 0;
const coords = new THREE.Vector3();
init();
render();
function init() {
const aspect = window.innerWidth / window.innerHeight;
camera = new THREE.OrthographicCamera(frustumSize * aspect / -2, frustumSize * aspect / 2, frustumSize / 2, frustumSize / -2, 0.1, 20);
camera.position.z = 5;
scene = new THREE.Scene();
const geometry = new THREE.BufferGeometry();
const positionAttribute = new THREE.BufferAttribute(new Float32Array(1000 * 3), 3); // allocate large enough buffer
positionAttribute.setUsage(THREE.DynamicDrawUsage);
geometry.setAttribute('position', positionAttribute);
const material = new THREE.LineBasicMaterial()
line = new THREE.Line(geometry, material);
scene.add(line);
// initial points
addPoint(0, 0, 0); // start point
addPoint(1, 0, 0); // current pointer coordinate
//
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
renderer.domElement.addEventListener('pointerdown', onPointerDown);
renderer.domElement.addEventListener('pointermove', onPointerMove);
window.addEventListener('resize', onWindowResize);
}
function addPoint(x, y, z) {
const positionAttribute = line.geometry.getAttribute('position');
positionAttribute.setXYZ(index, x, y, z);
positionAttribute.needsUpdate = true;
index++;
line.geometry.setDrawRange(0, index);
}
function updatePoint(x, y, z) {
const positionAttribute = line.geometry.getAttribute('position');
positionAttribute.setXYZ(index - 1, coords.x, coords.y, 0);
positionAttribute.needsUpdate = true;
}
function onPointerDown(event) {
coords.x = (event.clientX / window.innerWidth) * 2 - 1;
coords.y = -(event.clientY / window.innerHeight) * 2 + 1;
coords.z = (camera.near + camera.far) / (camera.near - camera.far);
coords.unproject(camera);
addPoint(coords.x, coords.y, 0);
render();
}
function onPointerMove(event) {
coords.x = (event.clientX / window.innerWidth) * 2 - 1;
coords.y = -(event.clientY / window.innerHeight) * 2 + 1;
coords.z = (camera.near + camera.far) / (camera.near - camera.far);
coords.unproject(camera);
updatePoint(coords.x, coords.y, 0)
render();
}
function onWindowResize() {
const aspect = window.innerWidth / window.innerHeight;
camera.left = -frustumSize * aspect / 2;
camera.right = frustumSize * aspect / 2;
camera.top = frustumSize / 2;
camera.bottom = -frustumSize / 2;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
function render() {
renderer.render(scene, camera);
}
body {
margin: 0;
}
<script src="https://cdn.jsdelivr.net/npm/three#0.144/build/three.min.js"></script>
The idea is to allocate a single large buffer to store all current and future points of a line. You then use setDrawRange() to define, what parts of the buffer should be rendered.
I'm basically trying to achieve a kaleidoscopic effect with just one side, but I'm working with lots of Points, so I'd like that to happen in the shader. However if there's a Threejs trick that mirrors half of the texture or the Points object, that would be great. I tried to apply transformation matrices but I can't get it to work.
I found an old KaleidoShader that requires the usage of EffectComposer, but I'd like to implement it manually myself (without EffectComposer) and I'm struggling to do so. I'm using an FBO and I tried adding the code from that shader in both my simulation and render shaders but it's having no effect at all. Do I have to add yet another FBO texture or is it possibile to do those calculations in one of the existing shaders?
For visual reference https://ma-hub.imgix.net/wp-images/2019/01/23205110/premiere-pro-mirror-effect.jpg
I've spent so much time without getting to the bottom of this, hopefully someone can point me in the right direction.
Thanks
I just followed this article
Pasting in the code from that repo seems to work
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="c"></canvas>
<script type="module">
import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/build/three.module.js';
import {EffectComposer} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/postprocessing/EffectComposer.js';
import {RenderPass} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/postprocessing/RenderPass.js';
import {ShaderPass} from 'https://threejsfundamentals.org/threejs/resources/threejs/r115/examples/jsm/postprocessing/ShaderPass.js';
import {GUI} from 'https://threejsfundamentals.org/threejs/../3rdparty/dat.gui.module.js';
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 5;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.z = 2;
const scene = new THREE.Scene();
{
const color = 0xFFFFFF;
const intensity = 2;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
const boxWidth = 1;
const boxHeight = 1;
const boxDepth = 1;
const geometry = new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth);
function makeInstance(geometry, color, x) {
const material = new THREE.MeshPhongMaterial({color});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
cube.position.x = x;
return cube;
}
const cubes = [
makeInstance(geometry, 0x44aa88, 0),
makeInstance(geometry, 0x8844aa, -2),
makeInstance(geometry, 0xaa8844, 2),
];
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));
// from:
// https://github.com/mistic100/three.js-examples/blob/master/LICENSE
const kaleidoscopeShader = {
uniforms: {
"tDiffuse": { value: null },
"sides": { value: 6.0 },
"angle": { value: 0.0 }
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`,
fragmentShader: `
uniform sampler2D tDiffuse;
uniform float sides;
uniform float angle;
varying vec2 vUv;
void main() {
vec2 p = vUv - 0.5;
float r = length(p);
float a = atan(p.y, p.x) + angle;
float tau = 2. * 3.1416 ;
a = mod(a, tau/sides);
a = abs(a - tau/sides/2.) ;
p = r * vec2(cos(a), sin(a));
vec4 color = texture2D(tDiffuse, p + 0.5);
gl_FragColor = color;
}
`
};
const kaleidoscopePass = new ShaderPass(kaleidoscopeShader);
kaleidoscopePass.renderToScreen = true;
composer.addPass(kaleidoscopePass);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const gui = new GUI();
gui.add(kaleidoscopePass.uniforms.sides, 'value', 0, 20).name('sides');
gui.add(kaleidoscopePass.uniforms.angle, 'value', 0, 6.28, 0.01).name('angle');
let then = 0;
function render(now) {
now *= 0.001; // convert to seconds
const deltaTime = now - then;
then = now;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
composer.setSize(canvas.width, canvas.height);
}
cubes.forEach((cube, ndx) => {
const speed = 1 + ndx * .1;
const rot = now * speed;
cube.rotation.x = rot;
cube.rotation.y = rot;
});
composer.render(deltaTime);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
There is a texture wrap mode that does mirroring.
texture.wrapS = texture.wrapT = THREE.MirroredRepeatWrapping
Does that help?
edit: Here's an example showing mirroredrepeatwrapping on both axis:
https://glitch.com/~three-mirroredrepeatwrapping
Since there is suprisingly almost no information on webGL (or I just don't know how to search for it), I have a question about how to transform a mouse coordinates to 3D coordinates, so to see where exactly on the screen I am clicking.
So my case is that I have a very simple skybox, the camera is positioned at [0, 0, 0] and I can look around it by clicking and dragging. What I want to do is be able to click somewhere on that skybox and know where I have clicked as I need to put an annotation (some text, or html element) on that position. And that html element must move and go out of view with me turning to another side. So what I need is a way to get a mouse click and find out which side of the cube I am clicking on and at what coordinates, so I can place the annotations correctly.
I am using a plain WebGL, I don't use THREE.js or anything like that. Since its just one cube, I can only assume finding the intersection won't be that hard and won't require extra libraries.
Well you're certainly right that it's hard to find an example ðŸ˜
A common webgl shader projects in 3D using code like either
gl_Position = matrix * position;
or
gl_Position = projection * modelView * position;
or
gl_Position = projection * view * world * position;
which are all the same thing basically. They take position and multiply it by a matrix to convert to clip space. You need to do the opposite to go the other way, take a position in clip space and covert back to position space which is
inverse (projection * view * world) * clipSpacePosition
So, take your 3D library and compute the inverse of the matrix you're passing to WebGL. For exmaple here is some code that is computing matrices to draw something using twgl's math library
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 10;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 4, -6];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
const world = m4.rotationY(time);
For a shader that is effectively doing this
gl_Position = viewProjection * world * position
So we need the inverse
const invMat = m4.inverse(m4.multiply(viewProjection, world));
Then we need a clip space ray. We're going from 2D to 3D so we'll make a ray that cuts through the frustum starting at zNear and ending at zFar by using -1 and +1 as our Z value
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const clipX = x / rect.width * 2 - 1;
const clipY = y / rect.height * -2 + 1;
const start = m4.transformPoint(invMat, [clipX, clipY, -1]);
const end = m4.transformPoint(invMat, [clipX, clipY, 1]);
... do something with start/end
});
start and end are now relative to position (the data in your geometry) so you now have to use some ray to triangle code in JavaScript to walk through all your triangles and see if the ray from start to end intersecs one or more of your triangles.
Note if all you want is a ray in world space, not position space then you'd use
const invMat = m4.inverse(viewProjection);
"use strict";
const vs = `
uniform mat4 u_world;
uniform mat4 u_viewProjection;
attribute vec4 position;
attribute vec2 texcoord;
attribute vec4 color;
varying vec4 v_position;
varying vec2 v_texcoord;
varying vec4 v_color;
void main() {
v_texcoord = texcoord;
v_color = color;
gl_Position = u_viewProjection * u_world * position;
}
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
varying vec4 v_color;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord) * v_color;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("#c").getContext("webgl");
// compiles shaders, links, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
const cubeArrays = twgl.primitives.createCubeVertices(1);
cubeArrays.color = {value: [0.2, 0.3, 1, 1]};
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
// for each array
const cubeBufferInfo = twgl.createBufferInfoFromArrays(gl, cubeArrays);
const numLines = 50;
const positions = new Float32Array(numLines * 3 * 2);
const colors = new Float32Array(numLines * 4 * 2);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
// for each array
const linesBufferInfo = twgl.createBufferInfoFromArrays(gl, {
position: positions,
color: colors,
texcoord: { value: [0, 0], },
});
const tex = twgl.createTexture(gl, {
minMag: gl.NEAREST,
format: gl.LUMINANCE,
src: [
255, 192,
192, 255,
],
});
let clipX = 0;
let clipY = 0;
let lineNdx = 0;
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 1;
const zFar = 10;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [Math.cos(time), Math.sin(time), 6];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
const world = m4.rotateX(m4.rotationY(1), 1);
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, cubeBufferInfo);
twgl.setUniformsAndBindTextures(programInfo, {
tex,
u_world: world,
u_viewProjection: viewProjection,
color: [0.2, 0.3, 1, 1],
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, cubeBufferInfo);
// add a line in world space
const invMat = m4.inverse(viewProjection);
const start = m4.transformPoint(invMat, [clipX, clipY, -1]);
const end = m4.transformPoint(invMat, [clipX, clipY, 1]);
const poffset = lineNdx * 3 * 2;
const coffset = lineNdx * 4 * 2;
const color = [Math.random(), Math.random(), Math.random(), 1];
positions.set(start, poffset);
positions.set(end, poffset + 3);
colors.set(color, coffset);
colors.set(color, coffset + 4);
gl.bindBuffer(gl.ARRAY_BUFFER, linesBufferInfo.attribs.position.buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, positions);
gl.bindBuffer(gl.ARRAY_BUFFER, linesBufferInfo.attribs.color.buffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, colors);
lineNdx = (lineNdx + 1) % numLines;
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, linesBufferInfo);
twgl.setUniformsAndBindTextures(programInfo, {
tex,
u_world: m4.identity(),
u_viewProjection: viewProjection,
color: [1, 0, 0, 1],
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, linesBufferInfo, gl.LINES);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
gl.canvas.addEventListener('mousemove', (e) => {
const canvas = gl.canvas;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
clipX = x / rect.width * 2 - 1;
clipY = y / rect.height * -2 + 1;
});
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas id="c"></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
As for WebGL info there is some here
I'm trying to smoothly scale cubes with random initial scaling. The problem is that after some short time cubes scale almost identically, besides I'm using random noise function for scale. What's wrong with my code?
Preview is here https://twilight-sweatshirt.glitch.me/
Live snippet https://stackblitz.com/edit/react-gakauv
As you can see cubes scaling becomes almost identical very fast.
Here's my code
const random = require("canvas-sketch-util/random");
const palettes = require("nice-color-palettes");
random.setSeed(19);
const palette = random.pick(palettes);
// Ensure ThreeJS is in global scope for the 'examples/'
global.THREE = require("three");
// Include any additional ThreeJS examples below
require("three/examples/js/controls/OrbitControls");
const canvasSketch = require("canvas-sketch");
const settings = {
// Make the loop animated
animate: true,
// duration: 5,
fps: 30,
playbackRate: "throttle",
// Get a WebGL canvas rather than 2D
context: "webgl",
attributes: {
antialias: true
}
};
const sketch = ({ context }) => {
//#region Scene setup
// Create a renderer
const renderer = new THREE.WebGLRenderer({
canvas: context.canvas
});
renderer.setClearColor("aquamarine", 1);
const camera = new THREE.OrthographicCamera();
const controls = new THREE.OrbitControls(camera, context.canvas);
const scene = new THREE.Scene();
scene.add(new THREE.AmbientLight(0x404040));
const light = new THREE.PointLight(0xffffff, 5, 15);
light.position.set(-1, 2, 4).multiplyScalar(1.5);
scene.add(light);
const light2 = new THREE.PointLight(0xffffff, 1.5, 15);
light2.position.set(3, 0, 2).multiplyScalar(1.5);
scene.add(light2);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const getNoise = (x, time) => {
return random.noise2D(x, time, 0.5) * 0.5 + 0.5;
};
for (let index = 0; index < 2; index++) {
const mesh = new THREE.Mesh(
geometry,
new THREE.MeshStandardMaterial({
color: random.pick(palette),
flatShading: true,
roughness: 0.75
})
);
mesh.name = `mesh-${index}`;
mesh.position.set(
random.range(-1, 1),
random.range(-1, 1),
random.range(-1, 1)
);
mesh.scale.set(
random.range(0.04, 0.5),
random.range(0.04, 0.5),
random.range(0.04, 0.5)
);
scene.add(mesh);
}
//#endregion
// draw each frame
return {
// Handle resize events here
resize({ pixelRatio, viewportWidth, viewportHeight }) {
renderer.setPixelRatio(pixelRatio);
renderer.setSize(viewportWidth, viewportHeight, false);
const aspect = viewportWidth / viewportHeight;
// Ortho zoom
const zoom = 1.5;
// Bounds
camera.left = -zoom * aspect;
camera.right = zoom * aspect;
camera.top = zoom;
camera.bottom = -zoom;
// Near/Far
camera.near = -100;
camera.far = 100;
// Set position & look at world center
camera.position.set(zoom, zoom, zoom);
camera.lookAt(new THREE.Vector3());
// Update the camera
camera.updateProjectionMatrix();
},
// Update & render your scene here
render({ time, playhead }) {
scene.children.forEach(obj => {
if (obj.isMesh !== true) {
return;
}
// console.log(`${obj.name}: `,getNoise(obj.scale.z, time));
// console.log(scene);
obj.scale.set(
// obj.scale.x,
// obj.scale.y,
// obj.scale.z,
getNoise(obj.scale.x, time),
getNoise(obj.scale.y, time),
getNoise(obj.scale.z, time)
);
});
controls.update();
renderer.render(scene, camera);
},
// Dispose of events & renderer for cleaner hot-reloading
unload() {
controls.dispose();
renderer.dispose();
}
};
};
canvasSketch(sketch, settings);
For the maximum calculation, the noise difference was much so it was flickering the box.
Try Setting these parameters
return random.noise2D(x, time, 0.8) * 0.2 + 0.5;
OR
return random.noise2D(x, time, 0.7) * 0.3 + 0.5;
OR
return random.noise2D(x, time, 0.6) * 0.4 + 0.5;
Choose as per your frequency.
See, if this is what your were looking for https://stackblitz.com/edit/react-hhurr3?file=index.js
I'm drawing a wind-turbine and I'm trying to figure out how I should draw the blades so they rotate around in a circle. Initially, I'm trying to draw one blade that rotates in a circle but I'm having a hard time understanding how to do it. All mine does is go till a certain angle and then comes back...here's my code :
var t = model_transform;
model_transform = t;
var temp = Math.PI/180;
var degreeOfRot = 45;
var time = Math.sin(graphics_state.animation_time/2000); //Represents the graphics time
model_transform = mult(model_transform, translation(0,0,0));
model_transform = mult(model_transform, rotation(degreeOfRot*time, 0.0,0.0,1.0));
model_transform = mult(model_transform, scale(0.3,0.9,0));
this.shapes.box.draw(graphics_state,model_transform,this.red);
I've been looking at other answers on stack and still haven't figured it out...I feel like its something to do with the math but not sure
I think you want something like (note: I'm using a unit cube centered around the origin so there's an extra scale to make it long and an extra translation to move the origin)
// spread the blades round the circle
const rotationOffset = i / settings.numBlades * Math.PI * 2;
// rotate around Z
world = m4.multiply(world, m4.rotationZ(rotationOffset + time));
// move it away from center
world = m4.multiply(world, m4.translation([settings.xoff, settings.yoff, 0]));
// scale the unit cube to be long in Y
world = m4.multiply(world, m4.scaling([1, 10, 1]));
// the unit cube is centered around the origin. It's 1 unit big
// so this tranlation will move it's origin to the bottom left edge
world = m4.multiply(world, m4.translation([-.5, .5, 0]));
You might find this article helpful and maybe this one or this one
const vs = `
uniform mat4 u_worldViewProjection;
attribute vec4 position;
void main() {
gl_Position = u_worldViewProjection * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
"use strict";
const m4 = twgl.m4;
const v3 = twgl.v3;
const gl = document.getElementById("c").getContext("webgl");
const settings = {
xoff: 0,
yoff: 0,
numBlades: 1,
};
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData for positions, texcoords
const centerBufferInfo = twgl.primitives.createSphereBufferInfo(gl, 1, 24, 12);
const bladeBufferInfo = twgl.primitives.createCubeBufferInfo(gl);
function render(time) {
time *= 0.001;
twgl.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
const fov = 45 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 100;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [0, 0, -40];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
gl.useProgram(programInfo.program);
// draw center
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, centerBufferInfo);
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
u_color: [1, 0.5, 1, 1],
u_worldViewProjection: m4.translate(viewProjection, [0, 0, 1]),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, centerBufferInfo);
// draw blades
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bladeBufferInfo);
for (let i = 0; i < settings.numBlades; ++i) {
let world = m4.identity();
// spread the blades round the circle
const rotationOffset = i / settings.numBlades * Math.PI * 2;
// rotate around Z
world = m4.multiply(world, m4.rotationZ(rotationOffset + time));
// move it away from center
world = m4.multiply(world, m4.translation([settings.xoff, settings.yoff, 0]));
// scale the unit cube to be long in Y
world = m4.multiply(world, m4.scaling([1, 10, 1]));
// the unit cube is centered around the origin. It's 1 unit big
// so this tranlation will move it's origin to the bottom left edge
world = m4.multiply(world, m4.translation([-.5, .5, 0]));
// calls gl.uniformXXX
twgl.setUniforms(programInfo, {
u_color: [1, 0, 0, 1],
u_worldViewProjection: m4.multiply(viewProjection, world),
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bladeBufferInfo);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
setupSlider("#xoffSlider", "#xoff", "xoff", 10);
setupSlider("#yoffSlider", "#yoff", "yoff", 10);
setupSlider("#bladesSlider", "#blades", "numBlades", 1);
function setupSlider(sliderId, labelId, property, divisor) {
const slider = document.querySelector(sliderId);
const label = document.querySelector(labelId);
function updateLabel() {
label.textContent = settings[property];
}
slider.addEventListener('input', e => {
settings[property] = parseInt(slider.value) / divisor;
updateLabel();
});
updateLabel();
slider.value = settings[property];
}
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
#ui {
position: absolute;
left: 10px;
top: 10px;
z-index: 2;
background: rgba(255, 255, 255, 0.9);
padding: .5em;
}
<script src="https://twgljs.org/dist/3.x/twgl-full.min.js"></script>
<canvas id="c"></canvas>
<div id="ui">
<div><input id="xoffSlider" type="range" min="0" max="100"/><label>xoff: <span id="xoff"></span></label></div>
<div><input id="yoffSlider" type="range" min="0" max="100"/><label>yoff: <span id="yoff"></span></label></div>
<div><input id="bladesSlider" type="range" min="1" max="20"/><label>blades: <span id="blades"></span></label></div>
</div>