Bistable auxetic material triangle maker (three.js)
(c) All rights reserved
by PhilQ
HTML
<script type="x-shader/x-vertex" id="vertexShader">
uniform vec2 resolution;
varying vec4 vPos;
varying vec3 vNormal;
varying vec2 vUv;
varying mat4 matModel;
varying mat4 matModelView;
varying mat4 matProjection;
varying mat3 matNormal;
void main() {
// Send to fragment shader
vNormal = normal;
vUv = uv;
vPos = vec4(position, 1.0);
matModel = modelMatrix;
matModelView = modelViewMatrix;
matProjection = projectionMatrix;
matNormal = normalMatrix;
// Local space (object)
gl_Position = vPos;
// to World space (scene)
gl_Position = modelMatrix * vPos;
// to View space (camera)
gl_Position = viewMatrix * modelMatrix * vPos;
// to Clip space
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vPos;
// Note: Three.js will convert to NDC space and then to Screen/Viewport space
}
</script>
<script type="x-shader/x-fragment" id="fragmentShader">
uniform vec2 resolution;
varying vec4 vPos;
varying vec3 vNormal;
varying vec2 vUv;
varying mat4 matModel;
varying mat4 matModelView;
varying mat4 matProjection;
varying mat3 matNormal;
void main() {
vec4 localUv = vec4(vUv, 0.0, 1.0); // Local uv, bottom-left (0,0) to top-right (1,1)
vec4 localPos = vPos;
vec4 worldPos = matModel * vPos;
vec4 viewPos = viewMatrix * matModel * vPos;
vec4 clipPos = matProjection * viewMatrix * matModel * vPos;
vec4 ndcPos = vec4(clipPos.xyz / clipPos.w, clipPos.w); // Added w to be able to back-calculate to clip space
vec2 screenPos = (ndcPos.xy + 1.0) * 0.5; // Screen uv, bottom-left (0,0) to top-right (1,1)
vec2 viewportPos = vec2(screenPos.x, -screenPos.y); // Top-left (0,0) to bottom-right (1,1)
vec2 pixelPos = screenPos * resolution; // Actual pixel coordinates
// gl_FragColor = vec4(screenPos.xy, 0.0, 1.0);
// Basic color + lighting
vec4 color = vec4(1.0, 0.0, 1.0, 1.0);
vec3 light = normalize( vec3( 0.5, 0.2, 1.0 ) ); // Normalized direction (like sunlight)
float...
CSS
body { margin: 0; }
canvas { width: 100%; height: 100% }
JavaScript
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
/*
Create custom triangle geometry split in segments like used in video:
https://www.youtube.com/watch?v=vrOjy-v5JgQ
For reference, read:
https://threejs.org/examples/#webgl_buffergeometry_indexed
https://learnopengl.com/Getting-started/Coordinate-Systems
https://www.songho.ca/opengl/gl_projectionmatrix.html
https://www.songho.ca/opengl/gl_transform.html
https://threejs.org/docs/#api/en/renderers/webgl/WebGLProgram
Perspective vs orthographic projection:
https://threejs.org/examples/?q=camera#webgl_camera
https://threejs.org/examples/?q=camera#webgl_camera_cinematic
*/
let camera, scene, renderer, controls, stats, gui;
let cube, triangle;
let running = true;
let geometry, vertices, indices, material;
const init = function () {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
camera.position.z = 10;
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
controls = new OrbitControls( camera, renderer.domElement );
controls.update();
stats = new Stats();
document.body.appendChild( stats.dom );
gui = new GUI();
const light = new THREE.HemisphereLight();
light.intensity = 3;
scene.add( light );
const axesHelper = new THREE.AxesHelper( 5 );
scene.add( axesHelper );
// On resize, update camera and renderer
window.addEventListener('resize', function(event) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
});
// Play/pause animation by pressing P
window.addEventListener('keydown',...