how to draw a 3d handle

From our sponsor: Try Mailchimp today.





In this tutorial you'll learn how to create a customizer app that lets you change the colors of a 3D model of a chair using Three.js.

3DModelCustomizer01

See the demo in action: 3D Model Colour Customizer App with 3.js

A quick introduction

This tool is built inspired past the Vans shoe customizer, and uses the astonishing JavaScript 3D library Three.js.

For this tutorial, I'll assume you are comfortable with JavaScript, HTML and CSS.

I'm going to do something a little flake unlike hither in the interest of actually teaching you, and non making you copy/paste parts that aren't all that relevant to this tutorial,we're going to start with all of the CSS in place. The CSS really is just for the dressing around the app, information technology focusses on the UI only. That being said, each time nosotros paste some HTML, I'll explain apace what the CSS does. Let's get started.

Part 1: The 3D model

If you want to skip this part entirely, feel costless to exercise so, but information technology may pay to read it just so you accept a deeper understanding of how everything works.

This isn't a 3D modelling tutorial, simply I will explain how the model is prepare up in Blender, and if you'd like to create something of your ain, change a free model you found somewhere online, or instruct someone you're commissioning. Here'due south some information virtually how our chairs 3D model is authored.

The 3D model for this tutorial is hosted and included inside the JavaScript, so don't worry about downloading or having to practice any of this unless you lot'd like to wait further into using Blender, and learning how to create your own model.

Scale

The scale is set to approximately what it would exist in the real earth; I don't know if this is important, but it feels like the correct affair to do, so why not?

blender-a

Layering and naming conventions

This partis important: each chemical element of the object you desire to customize independently needs to be its own object in the 3D scene, and each item needs to have a unique proper noun. Here we have dorsum, base, cushions, legs and supports. Note that if you have say, three items all called supports, Blender is going to name them every bit supports,supports.001, supports.002. That doesn't matter, considering in our JavaScript nosotros'll be usingincludes("supports")to find all of those objects that comprise the stringsupports in it.

blender-b

Placement

The model should exist placed at the world origin, ideally with its anxiety on the flooring. It should ideally exist facing the right way, but this tin hands be rotated via JavaScript, no impairment, no foul.

Setting upwardly for export

Before exporting, you lot want to utilise Blender'south Smart UV unwrap option. Without going too much into detail, this makes textures keep its aspect ratio in tact as it wraps around the unlike shapes in your model without stretching in weird ways (I'd advise reading upwardly on this option only if you're making your own model).

You desire to be sure to select all of your objects, and utilise your transformations. For instance, if you changed the calibration or transformed it in whatsoever way, you're telling Blender that this is the new 100% scale, instead of it all the same beingness 32.445% scale if yous scaled information technology down a flake.

File Format

Evidently Three.js supports a bunch of 3D object file formats, but the one it recommends is glTF (.glb). Blender supports this format as an export option, and then no worries there.

Part 2: Setting upward our surroundings

Get ahead and fork this pen, or offset your own 1 and copy the CSS from this pen. This is a blank pen with just the CSS we're going to be using in this tutorial.

Meet the Pen
3D Chair Customizer Tutorial – Blank by Kyle Wetton (@kylewetton)
on CodePen.

If you don't choose to fork this, grab the HTML as well; information technology has the responsive meta tags and Google fonts included.

We're going to apply three dependencies for this tutorial. I've included comments above each that draw what they do. Copy these into your HTML, right at the bottom:

<!-- The main Three.js file -->            <script src='https://unpkg.com/three@0.127.0/build/three.js          '></script>          <!-- This brings in the ability to load custom 3D objects in the .gltf file format. Blender allows the ability to export to this format out the box --> <script src='https://cdn.jsdelivr.net/gh/mrdoob/3.js@r92/examples/js/loaders/GLTFLoader.js'></script>  <!-- This is a uncomplicated to use extension for Three.js that activates all the rotating, dragging and zooming controls we need for both mouse and affect, there isn't a clear CDN for this that I can find --> <script src='https://threejs.org/examples/js/controls/OrbitControls.js'></script>        

Permit'south include the sheet element. The entire 3D experience gets rendered into this element, all other HTML will exist UI around this. Place the canvas at the bottom of your HTML, above your dependencies.

<!-- The canvas element is used to draw the 3D scene --> <sheet id="c"></canvas>

Now, we're going to create a new Scene for Three.js. In your JavaScript, lets brand a reference to this scene like so:

// Init the scene const scene = new THREE.Scene();

Beneath this, we're going to reference our canvas chemical element

const canvas = document.querySelector('#c');

Three.js requires a few things to run, and we will get to all of them. The first was scene, the second is a renderer. Let's add this below our canvas reference. This creates a new WebGLRenderer, nosotros're passing our sheet to it, and we've opted in for antialiasing, this creates smoother edges around our 3D model.

// Init the renderer const renderer = new THREE.WebGLRenderer({sheet, antialias: true});

And now we're going to append the renderer to the document body

document.body.appendChild(renderer.domElement);

The CSS for the sail chemical element is just stretching it to 100% summit and width of the body, then your entire page has now turned black, considering the unabridged sheet is now blackness!

Our scene is blackness, we're on the correct runway here.

The side by side thing 3.js needs is an update loop, basically this is a function that runs on each frame draw and is really of import to the manner our app will work. We've chosen our update function animate(). Let'southward add together it below everything else in our JavaScript.

function animate() {   renderer.render(scene, camera);   requestAnimationFrame(animate); }  animate();

Note that we're referencing a camera here, but nosotros haven't ready i upwards yet. Permit's add one now.

At the summit of your JavaScript, nosotros'll add together a variable called cameraFar. When we add together our camera to our scene, it's going to be added at position 0,0,0. Which is where our chair is sitting! so cameraFar is the variable that tells our camera how far off this mark to motion, then that nosotros can meet our chair.

var cameraFar = 5;

Now, above our function animate() {….} lets add a camera.

// Add together a camera var camera = new THREE.PerspectiveCamera( l, window.innerWidth / window.innerHeight, 0.one, one thousand); camera.position.z = cameraFar; camera.position.x = 0;

This is a perspective photographic camera, with the field of view of l, the size of the whole window/sheet, and some default clipping planes. The planes determine how near or far the photographic camera should be earlier the object isn't rendered. It's not something we need to pay attending to in our app.

Our scene is still black, allow's set up a background color.

At the top, in a higher place our scene reference, add a background colour variable chosen BACKGROUND_COLOR.

const BACKGROUND_COLOR = 0xf1f1f1;

Find how we used 0x instead of # in our hex? These are hexadecimal numbers, and the only thing you demand to recollect about that is that its not a string the way y'all'd handle a standard #hex variable in JavaScript. It's an integer and information technology starts with 0x.

Below our scene reference, allow'south update the scenes groundwork color, and add together some fog of the same color off in the distance, this is going to aid hide the edges of the floor once we add that in.

const BACKGROUND_COLOR = 0xf1f1f1;  // Init the scene const scene = new THREE.Scene();  // Prepare background scene.groundwork = new Iii.Colour(BACKGROUND_COLOR ); scene.fog = new THREE.Fog(BACKGROUND_COLOR, 20, 100);        

Now it's an empty world. It'south hard to tell that though, considering there'due south naught in there, zilch casting shadows. We have a blank scene. Now information technology'south time to load in our model.

Part iii: Loading the model

We're going to add together the function that loads in models, this is provided past our 2d dependency we added in our HTML.

Before we do that though, let's reference the model, we'll be using this variable quite a bit. Add this at the top of your JavaScript, above your BACKGROUND_COLOR. Allow's also add a path to the model. I've hosted information technology for us, it's about 1Mb in size.

var theModel; const MODEL_PATH =  "https://s3-us-west-2.amazonaws.com/s.cdpn.io/1376484/chair.glb";        

At present we tin create a new loader, and use the load method. This sets theModel as our 3D models unabridged scene. Nosotros're too going to set the size for this app, the right size seems to be nigh twice as big as it's loaded. Thirdly, we're going to offset the y position by -1 to bring it down a little scrap, and finally we're going to add together the model to the scene.

The first parameter is the model's filepath, the 2nd is a function that runs one time the resource is loaded, the third is undefined for at present but can exist used for a 2nd role that runs while the resource is loading, and the last parameter handles errors.

Add this beneath our camera.

// Init the object loader var loader = new Iii.GLTFLoader();  loader.load(MODEL_PATH, function(gltf) {   theModel = gltf.scene;  // Set up the models initial scale      theModel.scale.set(2,2,ii);    // First the y position a bit   theModel.position.y = -1;    // Add the model to the scene   scene.add together(theModel);  }, undefined, function(mistake) {   console.error(fault) });

At this point you should be seeing a stretched, blackness, pixelated chair. Every bit awful equally it looks, this is right so far. So don't worry!

model-loaded

Forth with a camera, we demand lights. The groundwork isn't affected by lights, but if we added a floor right now, it would also be black (night). At that place are a number of lights bachelor for Three.js, and a number of options to tweak all of them. We're going to add two: a hemisphere light, and a directional light. The settings are also sorted for our app, and they include position and intensity. This is something to play effectually with if you ever adopt these methods in your own app, simply for now, lets utilise the ones I've included. Add these lights below your loader.

// Add together lights var hemiLight = new Iii.HemisphereLight( 0xffffff, 0xffffff, 0.61 );     hemiLight.position.ready( 0, 50, 0 ); // Add hemisphere lite to scene    scene.add( hemiLight );  var dirLight = new THREE.DirectionalLight( 0xffffff, 0.54 );     dirLight.position.set( -8, 12, viii );     dirLight.castShadow = true;     dirLight.shadow.mapSize = new 3.Vector2(1024, 1024); // Add directional Light to scene         scene.add( dirLight );

Your chair looks marginally better! Before we continue, here's our JavaScript so far:

var cameraFar = 5; var theModel;  const MODEL_PATH =  "https://s3-the states-westward-2.amazonaws.com/due south.cdpn.io/1376484/chair.glb";  const BACKGROUND_COLOR = 0xf1f1f1; // Init the scene const scene = new THREE.Scene(); // Set background scene.background = new Iii.Colour(BACKGROUND_COLOR ); scene.fog = new THREE.Fog(BACKGROUND_COLOR, 20, 100);  const canvas = certificate.querySelector('#c');  // Init the renderer const renderer = new Iii.WebGLRenderer({sail, antialias: truthful});  document.torso.appendChild(renderer.domElement);  // Add a camerra var camera = new Iii.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, chiliad); camera.position.z = cameraFar; camera.position.x = 0;  // Init the object loader var loader = new 3.GLTFLoader();  loader.load(MODEL_PATH, function(gltf) {   theModel = gltf.scene;  // Set the models initial scale      theModel.calibration.gear up(2,2,2);    // Showtime the y position a scrap   theModel.position.y = -i;    // Add together the model to the scene   scene.add(theModel);  }, undefined, function(error) {   console.fault(mistake) });  // Add lights var hemiLight = new THREE.HemisphereLight( 0xffffff, 0xffffff, 0.61 );     hemiLight.position.set( 0, 50, 0 ); // Add hemisphere calorie-free to scene    scene.add( hemiLight );  var dirLight = new Three.DirectionalLight( 0xffffff, 0.54 );     dirLight.position.set( -8, 12, 8 );     dirLight.castShadow = truthful;     dirLight.shadow.mapSize = new Three.Vector2(1024, 1024); // Add directional Light to scene         scene.add together( dirLight );  part animate() {   renderer.return(scene, camera);   requestAnimationFrame(animate); }  animate();        

Here's what nosotros should be looking at right now:

with-lights

Let'south fix the pixelation and the stretching. Three.js needs to update the canvas size when it shifts, and it needs to set its internal resolution not only to the dimensions of the canvas, but also the device pixel ratio of the screen (which is much higher on phones).

Lets head to the bottom of our JavaScript, below where we phone call animate(), and add this function. This part basically listens to both, the canvass size and the window size, and returns a boolean depending on whether the two sizes are the same or not. Nosotros will use that part within the animate function to determine whether to re-return the scene. This function is too going to take into account the device pixel ratio to exist certain that the canvas is precipitous on mobile phones too.

Add together this function at the bottom of your JavaScript.

function resizeRendererToDisplaySize(renderer) {   const canvas = renderer.domElement;   var width = window.innerWidth;   var elevation = window.innerHeight;   var canvasPixelWidth = canvas.width / window.devicePixelRatio;   var canvasPixelHeight = sail.acme / window.devicePixelRatio;    const needResize = canvasPixelWidth !== width || canvasPixelHeight !== acme;   if (needResize) {          renderer.setSize(width, height, false);   }   return needResize; }        

At present update your animate function to look like this:

function breathing() {   renderer.render(scene, camera);   requestAnimationFrame(breathing);      if (resizeRendererToDisplaySize(renderer)) {     const canvas = renderer.domElement;     photographic camera.aspect = canvas.clientWidth / canvas.clientHeight;     photographic camera.updateProjectionMatrix();   } }        

Instantly, our chair is looking then much ameliorate!

Screen Shot 2019-09-16 at 6.49.13 PM

I demand to mention a couple things earlier we proceed:

  • The chair is backwards, this is my bad. We're going to merely rotate the model on its Y position
  • The supports are blackness? but the residue is white? This is because the model has some cloth information that has been imported with it that I had set up in Blender. This doesn't matter, considering nosotros're going to add together a function that lets us define textures in our app, and add together them to different areas of the chair when the model loads. So, if you accept a woods texture and a denim texture (spoiler: we volition), nosotros will take the ability to set these on load without the user having to choose them. So the materials on the chair right at present don't matter all that much.

Sense of humor me rapidly, head to the loader office, and call back where we ready the scale to (2,ii,2)? Lets add this under it:

// Set up the models initial scale      theModel.scale.ready(ii,2,2);    theModel.rotation.y = Math.PI;        

Aye, much better, lamentable most that. One more thing: 3.js doesn't have support for degrees equally far equally I know (?), everyone appears to be using Math.PI. This equals 180 degrees, so if yous want something angled at a 45 degree angle, you'd utilise Math.PI / 4.

rotated

Okay, we're getting there! We need a floor though, without a floor there tin can't really exist whatever shadows right?

Add a flooring, what we're doing here is creating a new airplane (a two-dimensional shape, or a three-dimensional shape with no height).

Add this beneath our lights…

// Flooring var floorGeometry = new THREE.PlaneGeometry(5000, 5000, 1, 1); var floorMaterial = new THREE.MeshPhongMaterial({   color: 0xff0000,   shininess: 0 });  var floor = new 3.Mesh(floorGeometry, floorMaterial); floor.rotation.x = -0.5 * Math.PI; floor.receiveShadow = true; flooring.position.y = -1; scene.add(flooring);        

Let'south take a look at whats happening here.

Offset, we made a geometry, we won't be needing to make another geometry in Iii.js in this tutorial, but you can brand all sorts.

Secondly, find how we as well made a new MeshPhongMaterial and prepare a couple options. It'due south color, and it's shininess. Check out some of Iii.js other materials later on. Phong is great considering you can conform its reflectiveness and specular highlights. There is also MeshStandardMaterial which has support for more avant-garde texture aspects such as metal and ambience apoplexy, and there is also the MeshBasicMaterial, which doesn't support shadows. We will just be creating Phong materials in this tutorial.

We created a variable called floor and merged the geometry and material into a Mesh.

We set the floor'south rotation to be flat, opted in for the power to receive shadows, moved it downwards the aforementioned way we moved the chair downward, and then added information technology to the scene.

We should at present be looking at this:

Screen Shot 2019-09-16 at 7.08.46 PM

Nosotros will leave it ruby-red for now, simply, where are the shadows? There's a couple of things nosotros demand to do for that. Showtime, under our const renderer, lets include a couple of options:

// Init the renderer const renderer = new THREE.WebGLRenderer({canvas, antialias: truthful});  renderer.shadowMap.enabled = true; renderer.setPixelRatio(window.devicePixelRatio);        

Nosotros've set the pixel ratio to whatever the device'due south pixel ratio is, not relevant to shadows, merely while nosotros're there, allow'southward do that. We've also enabled shadowMap, but there are still no shadows? That'due south considering the materials nosotros take on our chair are the ones brought in from Blender, and we desire to author some of them in our app.

Our loader part includes the ability to traverse the 3D model. So, head to our loader function and add together this in below the theModel = gltf.scene; line. For each object in our 3D model (legs, cushions, etc), we're going to to enable to selection to cast shadows, and to receive shadows. This traverse method will be used once more afterward.

Add this line below theModel = gltf.scene;

          theModel.traverse((o) => {      if (o.isMesh) {        o.castShadow = true;        o.receiveShadow = true;      }    });        

It looks arguably worse than it did before, but at least theres a shadow on the floor! This is because our model still has materials brought in from Blender. We're going to replace all of these materials with a basic, white PhongMaterial.

Lets create another PhongMaterial and add it above our loader function:

// Initial material const INITIAL_MTL = new 3.MeshPhongMaterial( { color: 0xf1f1f1, shininess: 10 } );        

This is a dandy starting material, it's a slight off-white, and it's just a little bit shiny. Absurd!

Nosotros could just add this to our chair and be done with it, but some objects may need a specific colour or texture on load, and we can't just coating the whole affair with the same base colour, the way we're going to do this is to add together this array of objects under our initial fabric.

// Initial material const INITIAL_MTL = new 3.MeshPhongMaterial( { color: 0xf1f1f1, shininess: 10 } );  const INITIAL_MAP = [   {childID: "back", mtl: INITIAL_MTL},   {childID: "base", mtl: INITIAL_MTL},   {childID: "cushions", mtl: INITIAL_MTL},   {childID: "legs", mtl: INITIAL_MTL},   {childID: "supports", mtl: INITIAL_MTL}, ];

We're going to traverse through our 3D model once again and use the childID to find unlike parts of the chair, and utilise the material to it (set up in the mtl belongings). These childID's match the names we gave each object in Blender, if you lot read that section, consider yourself informed!

Below our loader role, let's add together a function that takes the the model, the part of the object (type), and the material, and sets the material. We're likewise going to add a new property to this function called nameID and then that nosotros can reference information technology later.

// Function - Add the textures to the models function initColor(parent, type, mtl) {   parent.traverse((o) => {    if (o.isMesh) {      if (o.name.includes(type)) {           o.material = mtl;           o.nameID = type; // Set a new belongings to identify this object        }    }  }); }        

Now, inside our loader function, but before nosotros add our model to the scene (scene.add(theModel);)

Let'south run that role for each object in our INITIAL_MAP array:

          // Set initial textures   for (allow object of INITIAL_MAP) {     initColor(theModel, object.childID, object.mtl);   }        

Finally, caput back to our floor, and modify the color from red (0xff0000) to a calorie-free grey(0xeeeeee).

// Flooring var floorGeometry = new 3.PlaneGeometry(5000, 5000, 1, ane); var floorMaterial = new THREE.MeshPhongMaterial({   colour: 0xeeeeee, // <------- Here   shininess: 0 });        

It's worth mentioning here that 0xeeeeee is different to our background color. I manually dialed this in until the floor with the lights shining on it matched the lighter groundwork color. We're now looking at this:

Run across the Pen
3D Chair Customizer Tutorial – Part one by Kyle Wetton (@kylewetton)
on CodePen.

Congratulations, we've got this far! If you got stuck anywhere, fork this pen or investigate it until you observe the issue.

Office 4: Adding controls

For real though this is a very small office, and is super easy thank you to our third dependency OrbitControls.js.

In a higher place our animate office, we add this in our controls:

// Add controls var controls = new THREE.OrbitControls( camera, renderer.domElement ); controls.maxPolarAngle = Math.PI / 2; controls.minPolarAngle = Math.PI / 3; controls.enableDamping = true; controls.enablePan = false; controls.dampingFactor = 0.i; controls.autoRotate = false; // Toggle this if you'd like the chair to automatically rotate controls.autoRotateSpeed = 0.2; // 30        

Inside the animate office, at the top, add:

          controls.update();        

Then our controls variable is a new OrbitControls course. We've ready a few options that you can change here if you'd like. These include the range in which the user is allowed to rotate around the chair (above and below). We've disabled panning to keep the chair centered, enabled dampening to requite information technology weight, and included car rotate ability if you cull to use them. This is currently set to imitation.

Endeavor click and drag your chair, you should be able to explore the model with total mouse and bear upon functionality!

Encounter the Pen
Scrollable by Kyle Wetton (@kylewetton)
on CodePen.

Part 5: Changing colors

Our app currently doesn't exercise anything, so this side by side part volition focus on changing our colors. Nosotros're going to add a bit more HTML. Afterwards, I'll explain a chip about what the CSS is doing.

Add this below your canvas element:

<div class="controls"> <!-- This tray will exist filled with colors via JS, and the ability to slide this console will exist added in with a lightweight slider script (no dependency used for this) -->  <div id="js-tray" grade="tray">      <div id="js-tray-slide" class="tray__slide"></div>  </div> </div>        

Basically, the .controls DIV is stuck to the bottom of the screen, the .tray is set to be 100% width of the body, but its child, .tray__slide is going to make full with swatches and can exist every bit wide equally it needs. We're going to add the ability to slide this child to explore colors as i of the final steps of this tutorial.

Allow'south start past adding in a couple colors. At the pinnacle of our JavaScript, lets add an array of five objects, each with a color belongings.

const colors = [ {     colour: '66533C' }, {     color: '173A2F' }, {     color: '153944' }, {     color: '27548D' }, {     colour: '438AAC' }   ]

Note that these neither accept # or 0x to represent the hex. We will use these colors for both in functions. Likewise, information technology'south an object because we will be able to add together other properties to this color, similar shininess, or even a texture image (spoiler: nosotros will, and we will).

Lets make swatches out of these colors!

Get-go, let's reference our tray slider at the elevation of our JavaScript:

const TRAY = certificate.getElementById('js-tray-slide');        

Right at the lesser of our JavaScript, lets add a new role called buildColors and immediately phone call it.

// Part - Build Colors role buildColors(colors) {   for (permit [i, color] of colors.entries()) {     let swatch = document.createElement('div');     swatch.classList.add('tray__swatch');        swatch.fashion.background = "#" + color.color;      swatch.setAttribute('data-key', i);     TRAY.append(swatch);   } }  buildColors(colors);        

swatches

We're now creating swatches out of our colors array! Note that nosotros set the information-key attribute to the swatch, nosotros're going to use this to look up our colour and make them into materials.

Beneath our new buildColors office, permit'due south add an event handler to our swatches:

// Swatches const swatches = certificate.querySelectorAll(".tray__swatch");  for (const swatch of swatches) {   swatch.addEventListener('click', selectSwatch); }        

Our click handler calls a function called selectSwatch. This function is going to build a new PhongMaterial out of the color and call another office to traverse through our 3d model, find the part information technology'southward meant to change, and update information technology!

Below the issue handlers we just added, add the selectSwatch role:

office selectSwatch(e) {      allow color = colors[parseInt(e.target.dataset.key)];      let new_mtl;        new_mtl = new Iii.MeshPhongMaterial({           color: parseInt('0x' + color.color),           shininess: color.shininess ? color.shininess : ten                    });          setMaterial(theModel, 'legs', new_mtl); }        

This function looks up our color past its data-key aspect, and creates a new material out of it.

This won't piece of work yet, we need to add the setMaterial function, (see the final line of the role we just added).

Take note of this line: setMaterial(theModel, 'legs', new_mtl);. Currently nosotros're just passing 'legs' to this function, before long we will add the ability to alter out the dissimilar sections we desire to update. Only kickoff, lets add the zcode>setMaterial

role.

Below this function, add together the setMaterial part:

function setMaterial(parent, blazon, mtl) {   parent.traverse((o) => {    if (o.isMesh && o.nameID != null) {      if (o.nameID == blazon) {           o.textile = mtl;        }    }  }); }        

This function is similar to our initColor function, only with a few differences. It checks for the nameID we added in the initColor, and if its the same equally the parameter type, it adds the material to information technology.

Our swatches tin can at present create a new material, and change the colour of the legs, requite it a go! Here's everything we have then far in a pen. Investigate it if you're lost.

Come across the Pen
Swatches modify the legs color! by Kyle Wetton (@kylewetton)
on CodePen.

Part six: Selecting the parts to change

We tin can at present change the color of the legs, which is awesome, simply allow's add the ability to select the function our swatch should add its fabric to. Include this HTML but below the opening body tag, I'll explain the CSS below.

<!-- These toggle the the different parts of the chair that tin be edited, note information-option is the primal that links to the name of the part in the 3D file --> <div form="options">     <div form="option --is-active" data-option="legs">         <img src="https://s3-us-west-two.amazonaws.com/south.cdpn.io/1376484/legs.svg" alt=""/>     </div>     <div class="pick" information-option="cushions">         <img src="https://s3-u.s.-west-2.amazonaws.com/s.cdpn.io/1376484/cushions.svg" alt=""/>     </div>     <div grade="choice" information-option="base">         <img src="https://s3-us-due west-2.amazonaws.com/southward.cdpn.io/1376484/base.svg" alt=""/>     </div>     <div grade="option" data-option="supports">         <img src="https://s3-us-w-2.amazonaws.com/s.cdpn.io/1376484/supports.svg" alt=""/>     </div>     <div class="option" data-pick="back">         <img src="https://s3-us-west-two.amazonaws.com/s.cdpn.io/1376484/back.svg" alt=""/>     </div> </div>        

This is just a collection of buttons with custom icons in each. The .options DIV is stuck to the side of the screen via CSS (and shifts a bit with media queries). Each .option DIV is just a white square, that has a red border on it when a –is-agile class is added to it. Information technology also includes a data-pick attribute that matches our nameID, so we tin can identify information technology. Lastly, the image element has a CSS holding called pointer-events: none then that the event stays on the parent fifty-fifty if you click the paradigm.

Options set

Lets add another variable at the elevation of our JavaScript called activeOptions and by default permit's prepare it to 'legs':

var activeOption = 'legs';

Now head back to our selectSwatch function and update that hard-coded 'legs' parameter to activeOption

setMaterial(theModel, activeOption, new_mtl);

Now all we need to do is create a event handler to change out activeOption when an selection is clicked!

Let's add this above our const swatches and selectSwatch part.

// Select Option const options = document.querySelectorAll(".option");  for (const selection of options) {   option.addEventListener('click',selectOption); }  role selectOption(due east) {   let option = e.target;   activeOption = e.target.dataset.option;   for (const otherOption of options) {     otherOption.classList.remove('--is-active');   }   option.classList.add together('--is-active'); }        

We've added the selectOption role, which sets the activeOption to our issue targets information-option value, and toggles the –is-active form. Thats it!

Try it out

See the Pen
Changing options by Kyle Wetton (@kylewetton)
on CodePen.

Only why stop hither? An object could look like anything, information technology can't all be the aforementioned cloth. A chair with no wood or fabric? Lets expand our color selection a little scrap. Update your colour assortment to this:

const colors = [ {     texture: 'https://s3-us-due west-2.amazonaws.com/s.cdpn.io/1376484/wood.jpg',     size: [two,2,two],     shininess: 60 }, {     texture: 'https://s3-us-w-2.amazonaws.com/s.cdpn.io/1376484/denim.jpg',     size: [3, 3, 3],     shininess: 0 }, {     colour: '66533C' }, {     color: '173A2F' }, {     color: '153944' }, {     color: '27548D' }, {     color: '438AAC' }   ]

The elevation 2 are now textures. Nosotros've got wood and denim. We also take 2 new properties, size and shininess. Size is how often to echo a design, and so the larger the number, the more dense the pattern is, or more only put – the more it repeats.

In that location are two role we need to update to add together this power. Firstly, lets head to the buildColors part and update to this

// Function - Build Colors  function buildColors(colors) {   for (allow [i, colour] of colors.entries()) {     let swatch = document.createElement('div');     swatch.classList.add('tray__swatch');          if (color.texture)     {       swatch.style.backgroundImage = "url(" + color.texture + ")";        } else     {       swatch.style.background = "#" + color.color;     }      swatch.setAttribute('data-key', i);     TRAY.append(swatch);   } }        

Now its checking to see if its a texture, if it is, it'due south going to ready the swatches background to exist that texture, neat!

Screen Shot 2019-09-16 at 9.28.44 PM
Notice the gap betwixt the 5th and sixth swatch? The final batch of colors, which I volition provide, is grouped into color schemes of 5 colors per scheme. And then each scheme will have that modest divider in it, this is ready in the CSS and will make more sense in the final production.

The second part we're going to update is the selectSwatch function. Update information technology to this:

function selectSwatch(e) {      let color = colors[parseInt(e.target.dataset.cardinal)];      permit new_mtl;      if (color.texture) {              let txt = new THREE.TextureLoader().load(color.texture);              txt.repeat.set( color.size[0], colour.size[1], color.size[2]);       txt.wrapS = THREE.RepeatWrapping;       txt.wrapT = Iii.RepeatWrapping;              new_mtl = new 3.MeshPhongMaterial( {         map: txt,         shininess: colour.shininess ? color.shininess : 10       });         }      else     {       new_mtl = new 3.MeshPhongMaterial({           colour: parseInt('0x' + colour.color),           shininess: color.shininess ? color.shininess : 10                    });     }          setMaterial(theModel, activeOption, new_mtl); }        

To explicate what'southward going on here, this function will now check if it'south a texture. If it is, it's going to create a new texture using the Three.js TextureLoader method, it's going to fix the texture repeat using our size values, and set the wrapping of it (this wrapping option seems to work best, I've tried the others, then lets go with it), then its going to ready the PhongMaterials map property to the texture, and finally use the shininess value.

If it'southward non a texture, it uses our older method. Annotation that you can gear up a shininess property to any of our original colors!

Screen Shot 2019-09-16 at 9.50.02 PM

Important: if your textures but remain black when yous try add them. Cheque your console. Are you getting cross domain CORS errors? This is a CodePen bug and I've washed my all-time to try fix it. These assets are hosted direct in CodePen via a Pro feature so its unfortunate to have to boxing with this. Apparently, the best bet hither is to not visit those epitome URLs directly, otherwise I recommend signing upwardly to Cloudinary and using their free tier, you may have better luck pointing your textures there.

Here'southward a pen with the textures working on my end at least:

See the Pen
Texture support by Kyle Wetton (@kylewetton)
on CodePen.

Part seven: Finishing touches

I've had projects become run passed clients with a large button that is begging to exist pressed, positively glistening with temptation to fifty-fifty just hover over it, and them and their co-workers (Dave from accounts) come back with feedback about how they didn't know in that location was anything to exist pressed (screw y'all, Dave).

So let'southward add some calls to activity. First, let's chuck in a patch of HTML in a higher place the canvass element:

<!-- Just a quick find to the user that it can be interacted with --> <span form="drag-notice" id="js-elevate-notice">Drag to rotate 360&#176;</bridge>        

The CSS places this call-to-action to a higher place the chair, information technology's a nice large push button that instructs the user to drag to rotate the chair. It but stays there though? We will go to that.

Allow's spin the chair once it'south loaded beginning, and so, once the spin is done, permit's hibernate that call-to-action.

Starting time, lets add a loaded variable to the top of our JavaScript and set it to false:

var loaded = fake;        

Right at the lesser of your JavaScript, add this role

// Function - Opening rotate let initRotate = 0;  function initialRotation() {   initRotate++; if (initRotate <= 120) {     theModel.rotation.y += Math.PI / 60;   } else {     loaded = true;   } }        

This merely rotates the the model 360 degrees within the span of 120 frames (around ii seconds at 60fps), and we're going to run this in the animate role to telephone call it for 120 frames, once its done, it'southward going to set loaded to true in our animate role. Hither's how information technology will wait in its entirely with the new code at the terminate at that place:

office animate() {    controls.update();   renderer.render(scene, camera);   requestAnimationFrame(animate);      if (resizeRendererToDisplaySize(renderer)) {     const sheet = renderer.domElement;     photographic camera.aspect = canvas.clientWidth / sheet.clientHeight;     photographic camera.updateProjectionMatrix();   }      if (theModel != nada && loaded == false) {     initialRotation();   } }  animate();        

We check that theModel doesn't equal nix, and that the variable loaded is simulated, and we run that function for 120 frames, at which point the role switches to loaded = truthful, and our animate function ignores it.

You should have a nice spinning chair. When that chair stops is a corking time to remove our call-to-activeness.

In the CSS, there's a form that can be added to that call-to-action that will hide it with an animation, this animation has a delay of 3 seconds, then let's add that class at the same time the rotation starts.

At the meridian of your JavaScript nosotros volition reference it:

const DRAG_NOTICE = certificate.getElementById('js-drag-find');        

and update your animate function similar so

if (theModel != nil && loaded == false) {     initialRotation();     DRAG_NOTICE.classList.add together('start');   }        

Corking! Okay, here's some more colors, update your color assortment, I've give a lightweight sliding function beneath it:

const colors = [ {     texture: 'https://s3-us-w-2.amazonaws.com/s.cdpn.io/1376484/wood_.jpg',     size: [2,2,2],     shininess: 60 }, {     texture: 'https://s3-usa-west-2.amazonaws.com/s.cdpn.io/1376484/fabric_.jpg',     size: [4, 4, 4],     shininess: 0 }, {     texture: 'https://s3-u.s.-west-2.amazonaws.com/due south.cdpn.io/1376484/pattern_.jpg',     size: [8, viii, 8],     shininess: 10 }, {     texture: 'https://s3-us-west-2.amazonaws.com/due south.cdpn.io/1376484/denim_.jpg',     size: [three, iii, 3],     shininess: 0 }, {     texture: 'https://s3-u.s.a.-due west-two.amazonaws.com/south.cdpn.io/1376484/quilt_.jpg',     size: [six, 6, 6],     shininess: 0 }, {     colour: '131417'   }, {     colour: '374047'   }, {     colour: '5f6e78'   }, {     colour: '7f8a93'   }, {     color: '97a1a7'   }, {     color: 'acb4b9'   }, {     color: 'DF9998', }, {     color: '7C6862' }, {     color: 'A3AB84' }, {     colour: 'D6CCB1' }, {     color: 'F8D5C4' }, {     color: 'A3AE99' }, {     color: 'EFF2F2' }, {     color: 'B0C5C1' }, {     color: '8B8C8C' }, {     color: '565F59' }, {     colour: 'CB304A' }, {     color: 'FED7C8' }, {     color: 'C7BDBD' }, {     color: '3DCBBE' }, {     color: '264B4F' }, {     colour: '389389' }, {     color: '85BEAE' }, {     color: 'F2DABA' }, {     color: 'F2A97F' }, {     color: 'D85F52' }, {     colour: 'D92E37' }, {     color: 'FC9736' }, {     color: 'F7BD69' }, {     color: 'A4D09C' }, {     color: '4C8A67' }, {     colour: '25608A' }, {     color: '75C8C6' }, {     color: 'F5E4B7' }, {     color: 'E69041' }, {     colour: 'E56013' }, {     color: '11101D' }, {     color: '630609' }, {     color: 'C9240E' }, {     color: 'EC4B17' }, {     colour: '281A1C' }, {     color: '4F556F' }, {     colour: '64739B' }, {     color: 'CDBAC7' }, {     color: '946F43' }, {     color: '66533C' }, {     color: '173A2F' }, {     color: '153944' }, {     colour: '27548D' }, {     color: '438AAC' } ]        

Awesome! These hang off the page though, correct at the lesser of your JavaScript, add this function, it will allow you to drag the swatches console with mouse and touch. For the interest of keeping on topic, I won't delve too much into how information technology works.

var slider = certificate.getElementById('js-tray'), sliderItems = document.getElementById('js-tray-slide'), deviation;  function slide(wrapper, items) {   var posX1 = 0,       posX2 = 0,       posInitial,       threshold = 20,       posFinal,       slides = items.getElementsByClassName('tray__swatch');      // Mouse events   items.onmousedown = dragStart;      // Touch on events   items.addEventListener('touchstart', dragStart);   items.addEventListener('touchend', dragEnd);   items.addEventListener('touchmove', dragAction);     role dragStart (e) {     e = e || window.event;      posInitial = items.offsetLeft;      difference = sliderItems.offsetWidth - slider.offsetWidth;      divergence = departure * -ane;          if (eastward.type == 'touchstart') {       posX1 = e.touches[0].clientX;     } else {       posX1 = e.clientX;       document.onmouseup = dragEnd;       document.onmousemove = dragAction;     }   }    function dragAction (e) {     due east = e || window.effect;          if (e.type == 'touchmove') {       posX2 = posX1 - e.touches[0].clientX;       posX1 = e.touches[0].clientX;     } else {       posX2 = posX1 - e.clientX;       posX1 = e.clientX;     }          if (items.offsetLeft - posX2 <= 0 && items.offsetLeft - posX2 >= divergence) {         items.style.left = (items.offsetLeft - posX2) + "px";     }   }      function dragEnd (e) {     posFinal = items.offsetLeft;     if (posFinal - posInitial < -threshold) { } else if (posFinal - posInitial > threshold) {      } else {       items.style.left = (posInitial) + "px";     }      document.onmouseup = null;     certificate.onmousemove = nix;   }  }  slide(slider, sliderItems);        

At present, head to your CSS and nether .tray__slider, uncomment this small animation

/*   transform: translateX(-50%);   blitheness: wheelin 1s 2s ease-in-out forwards; */        

Okay, let'due south finish it off with a the last ii touches, and we're done!

Permit'southward update our .controls div to include this extra call-to-activity:

<div class="controls"> <div class="info">     <div class="info__message">         <p><strong>&nbsp;Catch&nbsp;</strong> to rotate chair. <strong>&nbsp;Scroll&nbsp;</strong> to zoom. <strong>&nbsp;Drag&nbsp;</strong> swatches to view more.</p>     </div> </div>  <!-- This tray will be filled with colors via JS, and the power to slide this panel volition be added in with a lightweight slider script (no dependency used for this) -->  <div id="js-tray" class="tray">      <div id="js-tray-slide" grade="tray__slide"></div>  </div> </div>        

Note that we have a new info section that includes some instructions on how to command the app.

Finally, allow's add a loading overlay then that our app is make clean while everything loads, and nosotros volition remove it once the model is loaded.

Add this to the top of our HTML, below the body tag.

<!-- The loading element overlays all else until the model is loaded, at which indicate we remove this element from the DOM -->   <div class="loading" id="js-loader"><div grade="loader"></div></div>        

Here's the matter nigh our loader, in order for it to load first, we're going to add the CSS to the head tag instead of existence included in the CSS. And so only add together this CSS just to a higher place the closing head tag.

            <style> .loading {   position: stock-still;   z-index: l;   width: 100%;   height: 100%;   peak: 0; left: 0;   background: #f1f1f1;   display: flex;   justify-content: center;   align-items: middle; }  .loader{   -webkit-perspective: 120px;   -moz-perspective: 120px;   -ms-perspective: 120px;   perspective: 120px;   width: 100px;   superlative: 100px; }  .loader:before{   content: "";   position: absolute;   left: 25px;   top: 25px;   width: 50px;   height: 50px;   background-color: #ff0000;   animation: flip 1s infinite; }  @keyframes flip {   0% {     transform: rotate(0);   }    50% {     transform: rotateY(180deg);   }    100% {     transform: rotateY(180deg)  rotateX(180deg);   } } </style>

Nearly in that location! Permit's remove it once the model is loaded.

At the superlative of our JavaScript, lets reference it:

const LOADER = document.getElementById('js-loader');        

Then in our loader function, subsequently scene.add(theModel), include this line

          // Remove the loader   LOADER.remove();        

Now our app loads backside this DIV, polishing it off:

Screen Shot 2019-09-16 at 10.31.25 PM

And that'due south information technology! Here's the completed pen for reference.

Come across the Pen
3D Chair Customizer Tutorial – Part four by Kyle Wetton (@kylewetton)
on CodePen.

Yous can besides check out the demo hosted here on Codrops.

Thank you for sticking with me!

This is a big tutorial. If you feel I fabricated a mistake somewhere, please permit me know in the comments, and thanks once again for following with me equally nosotros create this absolute unit.

condonminat1970.blogspot.com

Source: https://tympanus.net/codrops/2019/09/17/how-to-build-a-color-customizer-app-for-a-3d-model-with-three-js/

0 Response to "how to draw a 3d handle"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel