├── .npmrc ├── CHANGELOG.md ├── dist ├── .DS_Store └── threebox.css ├── docs ├── gallery.jpg └── soldieranimation.jpg ├── examples ├── README.md ├── 21-terrain.html ├── images │ ├── game.jpg │ ├── line.jpg │ ├── tube.jpg │ ├── basic.jpg │ ├── eiffel.jpg │ ├── statue.jpg │ ├── vuejs.jpg │ ├── animation.jpg │ ├── azuremaps.jpg │ ├── fixedzoom.jpg │ ├── logistics.jpg │ ├── mercator.jpg │ ├── object3D.jpg │ ├── raycaster.jpg │ ├── terrain.jpg │ ├── 3dbuildings.jpg │ ├── extrusions.jpg │ ├── multilayer.jpg │ ├── performance.jpg │ ├── stylechange.jpg │ ├── add-3d-model.jpg │ ├── alignmentTest.jpg │ ├── buildingshadow.jpg │ ├── eiffel-tower.png │ └── statue-of-liberty.png ├── models │ ├── Soldier.glb │ ├── plane │ │ └── plane.glb │ ├── radar │ │ └── 34M_17.glb │ ├── vehicles │ │ ├── car.glb │ │ └── truck.glb │ ├── history │ │ ├── elephant.glb │ │ └── triceratops.fbx │ ├── landmarks │ │ ├── eiffel.glb │ │ ├── spaceneedle.glb │ │ └── libertystatue.glb │ ├── windmill_a │ │ ├── windmill_a.bin │ │ └── textures │ │ │ └── material_baseColor.png │ └── Truck.mtl ├── css │ ├── free-fa-solid-900.woff2 │ ├── threebox.css │ ├── free-v4-font-face-min.css │ └── fontawesome.js ├── webfonts │ └── free-fa-solid-900.woff2 ├── config_template.js ├── 01-basic.html ├── 06-object3d.html ├── 02-line.html ├── 03-tube.html ├── 04-mercator.html ├── 10-stylechange.html ├── 17-azuremaps.html ├── 21-multifloor.html ├── 12-add3dmodel.html ├── 18-extrusions.html ├── 05-logistics.html ├── 08-3dbuildings.html ├── 14-buildingshadow.html ├── 07-alignmentTest.html ├── 16-multilayer.html ├── 09-raycaster.html ├── 19-fixedzoom.html ├── 15-performance.html ├── 20-game.html └── 11-animation.html ├── tests ├── unit │ ├── camera.test.js │ ├── validate.test.js │ ├── object.test.js │ ├── material.test.js │ └── utilities.test.js ├── threebox-tests.js └── threebox-tests.html ├── exports.js ├── src ├── objects │ ├── loaders │ │ └── GLTFLoader.js │ ├── tooltip.js │ ├── label.js │ ├── sphere.js │ ├── tube.js │ ├── Object3D.js │ ├── extrusion.js │ ├── LabelRenderer.js │ ├── loadObj.js │ ├── CSS2DRenderer.js │ └── effects │ │ └── BuildingShadows.js └── utils │ ├── ValueGenerator.js │ ├── constants.js │ ├── material.js │ └── validate.js ├── main.js ├── server.stop.js ├── .gitignore ├── .github ├── dependabot.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ ├── bug_report.md │ └── other-topics-and-questions.md ├── package.json ├── server.js ├── LICENSE.txt └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | scripts-prepend-node-path=true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/CHANGELOG.md -------------------------------------------------------------------------------- /dist/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/dist/.DS_Store -------------------------------------------------------------------------------- /docs/gallery.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/docs/gallery.jpg -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/README.md -------------------------------------------------------------------------------- /tests/unit/camera.test.js: -------------------------------------------------------------------------------- 1 | function cameraTest(instance){ 2 | 3 | 4 | } 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/21-terrain.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/21-terrain.html -------------------------------------------------------------------------------- /examples/images/game.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/game.jpg -------------------------------------------------------------------------------- /examples/images/line.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/line.jpg -------------------------------------------------------------------------------- /examples/images/tube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/tube.jpg -------------------------------------------------------------------------------- /docs/soldieranimation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/docs/soldieranimation.jpg -------------------------------------------------------------------------------- /examples/images/basic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/basic.jpg -------------------------------------------------------------------------------- /examples/images/eiffel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/eiffel.jpg -------------------------------------------------------------------------------- /examples/images/statue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/statue.jpg -------------------------------------------------------------------------------- /examples/images/vuejs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/vuejs.jpg -------------------------------------------------------------------------------- /exports.js: -------------------------------------------------------------------------------- 1 | window.Threebox = require('./src/Threebox.js'), 2 | window.THREE = require('./src/three.js') 3 | -------------------------------------------------------------------------------- /examples/images/animation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/animation.jpg -------------------------------------------------------------------------------- /examples/images/azuremaps.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/azuremaps.jpg -------------------------------------------------------------------------------- /examples/images/fixedzoom.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/fixedzoom.jpg -------------------------------------------------------------------------------- /examples/images/logistics.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/logistics.jpg -------------------------------------------------------------------------------- /examples/images/mercator.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/mercator.jpg -------------------------------------------------------------------------------- /examples/images/object3D.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/object3D.jpg -------------------------------------------------------------------------------- /examples/images/raycaster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/raycaster.jpg -------------------------------------------------------------------------------- /examples/images/terrain.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/terrain.jpg -------------------------------------------------------------------------------- /examples/models/Soldier.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/Soldier.glb -------------------------------------------------------------------------------- /examples/images/3dbuildings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/3dbuildings.jpg -------------------------------------------------------------------------------- /examples/images/extrusions.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/extrusions.jpg -------------------------------------------------------------------------------- /examples/images/multilayer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/multilayer.jpg -------------------------------------------------------------------------------- /examples/images/performance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/performance.jpg -------------------------------------------------------------------------------- /examples/images/stylechange.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/stylechange.jpg -------------------------------------------------------------------------------- /examples/models/plane/plane.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/plane/plane.glb -------------------------------------------------------------------------------- /examples/images/add-3d-model.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/add-3d-model.jpg -------------------------------------------------------------------------------- /examples/images/alignmentTest.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/alignmentTest.jpg -------------------------------------------------------------------------------- /examples/images/buildingshadow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/buildingshadow.jpg -------------------------------------------------------------------------------- /examples/images/eiffel-tower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/eiffel-tower.png -------------------------------------------------------------------------------- /examples/models/radar/34M_17.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/radar/34M_17.glb -------------------------------------------------------------------------------- /examples/models/vehicles/car.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/vehicles/car.glb -------------------------------------------------------------------------------- /examples/models/vehicles/truck.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/vehicles/truck.glb -------------------------------------------------------------------------------- /src/objects/loaders/GLTFLoader.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/src/objects/loaders/GLTFLoader.js -------------------------------------------------------------------------------- /examples/css/free-fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/css/free-fa-solid-900.woff2 -------------------------------------------------------------------------------- /examples/models/history/elephant.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/history/elephant.glb -------------------------------------------------------------------------------- /examples/models/landmarks/eiffel.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/landmarks/eiffel.glb -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | module.exports = exports = { 2 | Threebox: require('./src/Threebox'), 3 | THREE: require('./src/three.js') 4 | } -------------------------------------------------------------------------------- /examples/images/statue-of-liberty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/images/statue-of-liberty.png -------------------------------------------------------------------------------- /examples/models/history/triceratops.fbx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/history/triceratops.fbx -------------------------------------------------------------------------------- /examples/models/landmarks/spaceneedle.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/landmarks/spaceneedle.glb -------------------------------------------------------------------------------- /examples/models/windmill_a/windmill_a.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/windmill_a/windmill_a.bin -------------------------------------------------------------------------------- /examples/webfonts/free-fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/webfonts/free-fa-solid-900.woff2 -------------------------------------------------------------------------------- /examples/models/landmarks/libertystatue.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/landmarks/libertystatue.glb -------------------------------------------------------------------------------- /examples/config_template.js: -------------------------------------------------------------------------------- 1 | var config = { 2 | accessToken: 'mapbox key goes here', 3 | subscriptionKey: 'azure maps subscription key goes here' 4 | }; 5 | -------------------------------------------------------------------------------- /examples/models/windmill_a/textures/material_baseColor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jscastro76/threebox/HEAD/examples/models/windmill_a/textures/material_baseColor.png -------------------------------------------------------------------------------- /server.stop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var port = 8080 || process.env.PORT || 1337; 3 | 4 | const io = require('socket.io-client'); 5 | const socketClient = io.connect('http://localhost' + port); // Specify port if your express server is not using default port 80 6 | 7 | socketClient.on('connect', () => { 8 | socketClient.emit('npmStop'); 9 | setTimeout(() => { 10 | process.exit(0); 11 | }, 1000); 12 | }); 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # This .gitignore file was automatically created by Microsoft(R) Visual Studio. 3 | ################################################################################ 4 | 5 | /node_modules 6 | /obj/Debug 7 | /obj/ 8 | /bin/ 9 | /.vs/ 10 | /examples/config.js 11 | /.vs/ThreeboxSolution/v16 12 | /tests/node_modules 13 | /tests/obj/Debug 14 | /server.js 15 | /ThreeboxSolution.sln 16 | /ThreeboxSolution/ -------------------------------------------------------------------------------- /src/utils/ValueGenerator.js: -------------------------------------------------------------------------------- 1 | const ValueGenerator = function(input) { 2 | if(typeof input === 'object' && input.property !== undefined) // Value name comes from a property in each item 3 | return (f => f.properties[input.property]); 4 | else if(typeof input === 'object' && input.generator !== undefined) // Value name generated by a function run on each item 5 | return input.generator; 6 | else return (() => input); 7 | 8 | return undefined; 9 | } 10 | 11 | module.exports = exports = ValueGenerator; -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | -------------------------------------------------------------------------------- /src/objects/tooltip.js: -------------------------------------------------------------------------------- 1 | const utils = require("../utils/utils.js"); 2 | const Objects = require('./objects.js'); 3 | const CSS2D = require('./CSS2DRenderer.js'); 4 | var THREE = require("../three.js"); 5 | 6 | function Tooltip(obj) { 7 | 8 | obj = utils._validate(obj, Objects.prototype._defaults.tooltip); 9 | 10 | if (obj.text) { 11 | 12 | let divToolTip = Objects.prototype.drawTooltip(obj.text, obj.mapboxStyle); 13 | 14 | let tooltip = new CSS2D.CSS2DObject(divToolTip); 15 | tooltip.visible = false; 16 | tooltip.name = "tooltip"; 17 | var userScaleGroup = Objects.prototype._makeGroup(tooltip, obj); 18 | Objects.prototype._addMethods(userScaleGroup); 19 | return userScaleGroup; 20 | } 21 | 22 | } 23 | 24 | module.exports = exports = Tooltip; -------------------------------------------------------------------------------- /src/objects/label.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jscastro / https://github.com/jscastro76 3 | */ 4 | const utils = require("../utils/utils.js"); 5 | const Objects = require('./objects.js'); 6 | const CSS2D = require('./CSS2DRenderer.js'); 7 | 8 | function Label(obj) { 9 | 10 | obj = utils._validate(obj, Objects.prototype._defaults.label); 11 | 12 | let div = Objects.prototype.drawLabelHTML(obj.htmlElement, obj.cssClass); 13 | 14 | let label = new CSS2D.CSS2DObject(div); 15 | label.name = "label"; 16 | label.visible = obj.alwaysVisible; 17 | label.alwaysVisible = obj.alwaysVisible; 18 | var userScaleGroup = Objects.prototype._makeGroup(label, obj); 19 | Objects.prototype._addMethods(userScaleGroup); 20 | userScaleGroup.visibility = obj.alwaysVisible; 21 | 22 | return userScaleGroup; 23 | } 24 | 25 | 26 | module.exports = exports = Label; -------------------------------------------------------------------------------- /src/objects/sphere.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author peterqliu / https://github.com/peterqliu 3 | * @author jscastro / https://github.com/jscastro76 4 | */ 5 | const utils = require("../utils/utils.js"); 6 | const material = require("../utils/material.js"); 7 | const THREE = require('../three.js'); 8 | const Objects = require('./objects.js'); 9 | const Object3D = require('./Object3D.js'); 10 | 11 | function Sphere(opt) { 12 | 13 | opt = utils._validate(opt, Objects.prototype._defaults.sphere); 14 | let geometry = new THREE.SphereBufferGeometry(opt.radius, opt.sides, opt.sides); 15 | let mat = material(opt) 16 | let output = new THREE.Mesh(geometry, mat); 17 | //[jscastro] we convert it in Object3D to add methods, bounding box, model, tooltip... 18 | return new Object3D({ obj: output, units: opt.units, anchor: opt.anchor, adjustment: opt.adjustment, bbox: opt.bbox, tooltip: opt.tooltip, raycasted: opt.raycasted }); 19 | 20 | } 21 | 22 | 23 | module.exports = exports = Sphere; -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: ":green_apple: feature" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | **Is your feature request related to a problem? Please describe.** 17 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 18 | 19 | **Describe the solution you'd like** 20 | A clear and concise description of what you want to happen. 21 | 22 | **Describe alternatives you've considered** 23 | A clear and concise description of any alternative solutions or features you've considered. 24 | 25 | **Additional context** 26 | Add any other context or screenshots about the feature request here. 27 | -------------------------------------------------------------------------------- /src/objects/tube.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author peterqliu / https://github.com/peterqliu 3 | * @author jscastro / https://github.com/jscastro76 4 | */ 5 | const utils = require("../utils/utils.js"); 6 | const material = require("../utils/material.js"); 7 | const Objects = require('./objects.js'); 8 | const THREE = require("../three.js"); 9 | const Object3D = require('./Object3D.js'); 10 | 11 | function tube(opt, world){ 12 | 13 | // validate and prep input geometry 14 | opt = utils._validate(opt, Objects.prototype._defaults.tube); 15 | 16 | let points = [] 17 | opt.geometry.forEach(p => { 18 | points.push(new THREE.Vector3(p[0], p[1], p[2])); 19 | }) 20 | const curve = new THREE.CatmullRomCurve3(points); 21 | let tube = new THREE.TubeGeometry(curve, points.length, opt.radius, opt.sides, false); 22 | let mat = material(opt); 23 | let obj = new THREE.Mesh(tube, mat); 24 | //[jscastro] we convert it in Object3D to add methods, bounding box, model, tooltip... 25 | return new Object3D({ obj: obj, units: opt.units, anchor: opt.anchor, adjustment: opt.adjustment, bbox: opt.bbox, tooltip: opt.tooltip, raycasted: opt.raycasted }); 26 | } 27 | 28 | module.exports = exports = tube; 29 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: ":beetle: bug" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 15 | 16 | **Describe the bug** 17 | A clear and concise description of what the bug is. 18 | 19 | **To Reproduce** 20 | Steps to reproduce the behavior: 21 | 1. Go to '...' 22 | 2. Click on '....' 23 | 3. Scroll down to '....' 24 | 4. See error 25 | 26 | **Expected behavior** 27 | A clear and concise description of what you expected to happen. 28 | 29 | **Relevant Code or Code Sandbox** 30 | Share the relevant code producing the bug or provide a Minimal Reproducible Example on [fiddle](https://jsfiddle.net/), [codepen](https://codepen.io/) or any other code sandbox. 31 | 32 | **Console Results** 33 | Share the browser console results 34 | 35 | **Screenshots** 36 | If applicable, add screenshots to help explain your problem. 37 | 38 | - [Version [e.g. 2.1.7] 39 | 40 | **Additional context** 41 | Add any other context about the problem here. 42 | -------------------------------------------------------------------------------- /src/objects/Object3D.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author peterqliu / https://github.com/peterqliu 3 | * @author jscastro / https://github.com/jscastro76 4 | */ 5 | const Objects = require('./objects.js'); 6 | const utils = require("../utils/utils.js"); 7 | 8 | function Object3D(opt) { 9 | opt = utils._validate(opt, Objects.prototype._defaults.Object3D); 10 | // [jscastro] full refactor of Object3D to behave exactly like 3D Models loadObj 11 | let obj = opt.obj; 12 | // [jscastro] options.rotation was wrongly used 13 | const r = utils.types.rotation(opt.rotation, [0, 0, 0]); 14 | const s = utils.types.scale(opt.scale, [1, 1, 1]); 15 | obj.rotation.set(r[0], r[1], r[2]); 16 | obj.scale.set(s[0], s[1], s[2]); 17 | obj.name = "model"; 18 | let userScaleGroup = Objects.prototype._makeGroup(obj, opt); 19 | opt.obj.name = "model"; 20 | Objects.prototype._addMethods(userScaleGroup); 21 | //[jscastro] calculate automatically the pivotal center of the object 22 | userScaleGroup.setAnchor(opt.anchor); 23 | //[jscastro] override the center calculated if the object has adjustments 24 | userScaleGroup.setCenter(opt.adjustment); 25 | //[jscastro] if the object is excluded from raycasting 26 | userScaleGroup.raycasted = opt.raycasted; 27 | userScaleGroup.visibility = true; 28 | 29 | return userScaleGroup 30 | } 31 | 32 | module.exports = exports = Object3D; -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | const WORLD_SIZE = 1024000; //TILE_SIZE * 2000 2 | const MERCATOR_A = 6378137.0; // 900913 projection property. (Deprecated) Replaced by EARTH_RADIUS 3 | const FOV_ORTHO = 0.1 / 180 * Math.PI; //Mapbox doesn't accept 0 as FOV 4 | const FOV = Math.atan(3 / 4); //from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/main/src/geo/transform.js#L93 5 | const EARTH_RADIUS = 6371008.8; //from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/0063cbd10a97218fb6a0f64c99bf18609b918f4c/src/geo/lng_lat.js#L11 6 | const EARTH_CIRCUMFERENCE_EQUATOR = 40075017 //from Mapbox https://github.com/mapbox/mapbox-gl-js/blob/0063cbd10a97218fb6a0f64c99bf18609b918f4c/src/geo/lng_lat.js#L117 7 | 8 | module.exports = exports = { 9 | WORLD_SIZE: WORLD_SIZE, 10 | PROJECTION_WORLD_SIZE: WORLD_SIZE / (EARTH_RADIUS * Math.PI * 2), 11 | MERCATOR_A: EARTH_RADIUS, 12 | DEG2RAD: Math.PI / 180, 13 | RAD2DEG: 180 / Math.PI, 14 | EARTH_RADIUS: EARTH_RADIUS, 15 | EARTH_CIRCUMFERENCE: 2 * Math.PI * EARTH_RADIUS, //40075000, // In meters 16 | EARTH_CIRCUMFERENCE_EQUATOR: EARTH_CIRCUMFERENCE_EQUATOR, 17 | FOV_ORTHO: FOV_ORTHO, // closest to 0 18 | FOV: FOV, // Math.atan(3/4) radians. If this value is changed, FOV_DEGREES must be calculated 19 | FOV_DEGREES: FOV * 180 / Math.PI, // Math.atan(3/4) in degrees 20 | TILE_SIZE: 512 21 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other-topics-and-questions.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Other topics and questions 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: ":question: question" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 22 | 23 | **Threebox version**: 24 | 25 | ### Question 26 | -------------------------------------------------------------------------------- /dist/threebox.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | #map { 7 | position: absolute; 8 | top: 0; 9 | bottom: 0; 10 | width: 100%; 11 | } 12 | 13 | .helpDiv { 14 | width: auto; 15 | left: 50%; 16 | top: 0px; 17 | z-index: 2; 18 | position: absolute; 19 | } 20 | 21 | .help { 22 | background: rgba(0, 0, 0, 0.5); 23 | color: #fff; 24 | position: relative; 25 | text-align: center; 26 | top: 10px; 27 | left: -50%; 28 | padding: 5px 10px; 29 | margin: 0; 30 | font-size: 11px; 31 | line-height: 18px; 32 | border-radius: 3px; 33 | z-index: 1; 34 | display: block; 35 | } 36 | 37 | /*these 3 clases will provide mapbox-like style for labels*/ 38 | .toolTip { 39 | border: 0.5px black solid; 40 | display: inline-block; 41 | background: white; 42 | padding: 1px 6px; 43 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 44 | font-size: 11px !important; 45 | } 46 | 47 | .marker { 48 | max-width: 240px; 49 | display: flex; 50 | margin-bottom: 5em; 51 | text-align: center; 52 | color: black; 53 | } 54 | 55 | .mapboxgl-popup-tip { 56 | margin-top: -1px; 57 | } 58 | 59 | .top-right-tools { 60 | display: flex; 61 | right: 40px; 62 | z-index: 10; 63 | } 64 | 65 | .tools-i.mapboxgl-ctrl { 66 | margin: 10px 20px 0 0; 67 | display: inline-flex; 68 | } 69 | 70 | #toolsControl { 71 | margin: 10px 20px 0 0; 72 | display: inline-flex; 73 | } 74 | 75 | #toolsControl.mapboxgl-ctrl-group button + button, #toolsControl.mapboxgl-ctrl-group div + div { 76 | border-left: 1px solid #ddd; 77 | border-top: 0px; 78 | } -------------------------------------------------------------------------------- /examples/css/threebox.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | 6 | #map { 7 | position: absolute; 8 | top: 0; 9 | bottom: 0; 10 | width: 100%; 11 | } 12 | 13 | .helpDiv { 14 | width: auto; 15 | left: 50%; 16 | top: 0px; 17 | z-index: 2; 18 | position: absolute; 19 | } 20 | 21 | .help { 22 | background: rgba(0, 0, 0, 0.5); 23 | color: #fff; 24 | position: relative; 25 | text-align: center; 26 | top: 10px; 27 | left: -50%; 28 | padding: 5px 10px; 29 | margin: 0; 30 | font-size: 11px; 31 | line-height: 18px; 32 | border-radius: 3px; 33 | z-index: 1; 34 | display: block; 35 | } 36 | 37 | /*these 3 clases will provide mapbox-like style for labels*/ 38 | .toolTip { 39 | border: 0.5px black solid; 40 | display: inline-block; 41 | background: white; 42 | padding: 1px 6px; 43 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 44 | font-size: 11px !important; 45 | } 46 | 47 | .marker { 48 | max-width: 240px; 49 | display: flex; 50 | margin-bottom: 5em; 51 | text-align: center; 52 | color: black; 53 | } 54 | 55 | .mapboxgl-popup-tip { 56 | margin-top: -1px; 57 | } 58 | 59 | .top-right-tools { 60 | display: flex; 61 | right: 40px; 62 | z-index: 10; 63 | } 64 | 65 | .tools-i.mapboxgl-ctrl { 66 | margin: 10px 20px 0 0; 67 | display: inline-flex; 68 | } 69 | 70 | #toolsControl { 71 | margin: 10px 20px 0 0; 72 | display: inline-flex; 73 | } 74 | 75 | #toolsControl.mapboxgl-ctrl-group button + button, #toolsControl.mapboxgl-ctrl-group div + div { 76 | border-left: 1px solid #ddd; 77 | border-top: 0px; 78 | } -------------------------------------------------------------------------------- /tests/threebox-tests.js: -------------------------------------------------------------------------------- 1 | window.test = require('tape'), 2 | window.Threebox = require("../src/Threebox.js"), 3 | window.THREE = require("../src/three.js"); 4 | 5 | //window.runTests = function () { 6 | // material(instance); 7 | //} 8 | 9 | function vector3Equals(t, input, expected, allowableError, epsilon) { 10 | // Check that two Vector3s are equal to each other, allowing for a certain percentage of error due to floating point math 11 | if (allowableError === undefined) allowableError = 0.0000001; 12 | if (epsilon === undefined) epsilon = 0.00000000000001; 13 | var dX, dY, dZ; 14 | dX = Math.abs(input.x - expected.x) / (expected.x === 0 ? 1 : expected.x); 15 | dY = Math.abs(input.y - expected.y) / (expected.y === 0 ? 1 : expected.y); 16 | dZ = Math.abs(input.z - expected.z) / (expected.z === 0 ? 1 : expected.z); 17 | 18 | if (dX < epsilon) dX = 0; 19 | if (dY < epsilon) dY = 0; 20 | if (dZ < epsilon) dZ = 0; 21 | 22 | if (dX > allowableError || dY > allowableError || dZ > allowableError) { 23 | t.fail("Vector3 Equivalence failed: (" + input.x + ", " + input.y + ", " + input.z + ") expected: (" + expected.x + ", " + expected.y + ", " + expected.z + ")"); 24 | console.log(dY); 25 | } 26 | t.pass("ok Vector3 equivalence"); 27 | } 28 | 29 | 30 | function precisionRound(number, precision) { 31 | var factor = Math.pow(10, precision); 32 | var tempNumber = number * factor; 33 | var roundedTempNumber = Math.round(tempNumber); 34 | return roundedTempNumber / factor; 35 | }; 36 | 37 | -------------------------------------------------------------------------------- /examples/models/Truck.mtl: -------------------------------------------------------------------------------- 1 | # Blender MTL File: 'Truck.blend' 2 | # Material Count: 7 3 | 4 | newmtl Black 5 | Ns 96.078431 6 | Ka 1.000000 1.000000 1.000000 7 | Kd 0.013347 0.013347 0.013347 8 | Ks 0.500000 0.500000 0.500000 9 | Ke 0.000000 0.000000 0.000000 10 | Ni 1.000000 11 | d 1.000000 12 | illum 2 13 | 14 | newmtl Cream 15 | Ns 96.078431 16 | Ka 1.000000 1.000000 1.000000 17 | Kd 0.565467 0.640000 0.496259 18 | Ks 0.500000 0.500000 0.500000 19 | Ke 0.000000 0.000000 0.000000 20 | Ni 1.000000 21 | d 1.000000 22 | illum 2 23 | 24 | newmtl Silver 25 | Ns 96.078431 26 | Ka 1.000000 1.000000 1.000000 27 | Kd 0.385546 0.385546 0.385546 28 | Ks 0.500000 0.500000 0.500000 29 | Ke 0.000000 0.000000 0.000000 30 | Ni 1.000000 31 | d 1.000000 32 | illum 2 33 | 34 | newmtl Wheel_Rim 35 | Ns 96.078431 36 | Ka 1.000000 1.000000 1.000000 37 | Kd 0.640000 0.640000 0.640000 38 | Ks 0.500000 0.500000 0.500000 39 | Ke 0.000000 0.000000 0.000000 40 | Ni 1.000000 41 | d 1.000000 42 | illum 2 43 | 44 | newmtl Wheels 45 | Ns 96.078431 46 | Ka 1.000000 1.000000 1.000000 47 | Kd 0.047401 0.047401 0.047401 48 | Ks 0.500000 0.500000 0.500000 49 | Ke 0.000000 0.000000 0.000000 50 | Ni 1.000000 51 | d 1.000000 52 | illum 2 53 | 54 | newmtl White 55 | Ns 96.078431 56 | Ka 1.000000 1.000000 1.000000 57 | Kd 0.800000 0.800000 0.800000 58 | Ks 0.500000 0.500000 0.500000 59 | Ke 0.000000 0.000000 0.000000 60 | Ni 1.000000 61 | d 1.000000 62 | illum 2 63 | 64 | newmtl Window 65 | Ns 96.078431 66 | Ka 1.000000 1.000000 1.000000 67 | Kd 0.362586 0.585789 0.640000 68 | Ks 0.500000 0.500000 0.500000 69 | Ke 0.000000 0.000000 0.000000 70 | Ni 1.000000 71 | d 1.000000 72 | illum 2 73 | -------------------------------------------------------------------------------- /src/utils/material.js: -------------------------------------------------------------------------------- 1 | // This module creates a THREE material from the options object provided into the Objects class. 2 | // Users can do this in one of three ways: 3 | 4 | // - provide a preset THREE.Material in the `material` parameter 5 | // - specify a `material` string, `color`, and/or `opacity` as modifications of the default material 6 | // - provide none of these parameters, to use the default material 7 | 8 | var utils = require("../utils/utils.js"); 9 | var THREE = require("../three.js"); 10 | 11 | var defaults = { 12 | material: 'MeshBasicMaterial', 13 | color: 'black', 14 | opacity: 1 15 | }; 16 | 17 | 18 | function material (options) { 19 | 20 | var output; 21 | 22 | if (options) { 23 | 24 | options = utils._validate(options, defaults); 25 | 26 | // check if user provided material object 27 | if (options.material && options.material.isMaterial) output = options.material; 28 | 29 | // check if user provided any material parameters. create new material object based on that. 30 | else if (options.material || options.color || options.opacity){ 31 | output = new THREE[options.material]({color: options.color, transparent: options.opacity<1}); 32 | } 33 | 34 | // if neither, return default material 35 | else output = generateDefaultMaterial(); 36 | 37 | output.opacity = options.opacity; 38 | if (options.side) output.side = options.side 39 | 40 | } 41 | 42 | // if no options, return default 43 | else output = generateDefaultMaterial(); 44 | 45 | function generateDefaultMaterial(){ 46 | return new THREE[defaults.material]({color: defaults.color}); 47 | } 48 | 49 | return output 50 | } 51 | 52 | module.exports = exports = material; 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threebox-plugin", 3 | "version": "2.2.7", 4 | "description": "A Three.js plugin for Mapbox GL JS, using the CustomLayerInterface feature. Provides convenient methods to manage objects in lnglat coordinates, and to synchronize the map and scene cameras.", 5 | "main": "main.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/jscastro76/threebox.git" 9 | }, 10 | "author": " @jscastro76, @peterqliu, @kronick", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/jscastro76/threebox/issues" 14 | }, 15 | "homepage": "https://github.com/jscastro76/threebox#readme", 16 | "scripts": { 17 | "build": "browserify -p tinyify exports.js > dist/threebox.min.js && ncp ./examples/css/threebox.css ./dist/threebox.css --stopOnErr", 18 | "dev": "watchify exports.js --verbose -o dist/threebox.js ", 19 | "all": "browserify -p tinyify exports.js > dist/threebox.min.js && watchify exports.js --verbose -o dist/threebox.js ", 20 | "test": "browserify tests/threebox-tests.js > tests/threebox-tests-bundle.js", 21 | "start": "node server.js", 22 | "stop": "node server.stop.js" 23 | }, 24 | "directories": { 25 | "doc": "docs", 26 | "example": "examples", 27 | "test": "tests" 28 | }, 29 | "devDependencies": { 30 | "browserify": "^17.0.0", 31 | "ncp": "^2.0.0", 32 | "tape": "^5.1.1", 33 | "tinyify": "^4.0.0", 34 | "uglifyify": "^5.0.2", 35 | "watchify": "^4.0.0" 36 | }, 37 | "keywords": [ 38 | "three.js", 39 | "mapbox", 40 | "mapbox-gl-js", 41 | "azure-maps", 42 | "3D" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /tests/unit/validate.test.js: -------------------------------------------------------------------------------- 1 | function validateTest(instance){ 2 | 3 | var v = instance.utils.Validator; 4 | 5 | // Coords validation 6 | 7 | test('VALIDATE invalid Coords', function(t){ 8 | 9 | t.error(v.Coords(true), 'error'); 10 | t.error(v.Coords(['a']), 'error'); 11 | t.error(v.Coords([1]), 'error'); 12 | t.error(v.Coords([3, false]), 'error'); 13 | 14 | t.end(); 15 | 16 | }); 17 | 18 | test('VALIDATE valid Coords', function(t){ 19 | 20 | t.deepEqual(v.Coords([22,33]), [22,33]); 21 | t.deepEqual(v.Coords([22,33,-10]), [22,33, -10]); 22 | t.end(); 23 | 24 | }); 25 | 26 | // Line validation 27 | 28 | test('VALIDATE invalid Line', function(t){ 29 | 30 | t.error(v.Line([[1,2], [false, 4]]), 'error'); 31 | t.end(); 32 | 33 | }); 34 | 35 | test('VALIDATE valid Line', function(t){ 36 | 37 | t.deepEqual(v.Line([[-100, 20], [22,33]]), [[-100, 20], [22,33]]); 38 | t.end(); 39 | 40 | }); 41 | 42 | // Rotation validation 43 | 44 | test('VALIDATE invalid Rotation', function(t){ 45 | 46 | t.error(v.Rotation('rotate'), 'error'); 47 | t.end(); 48 | 49 | }); 50 | 51 | test('VALIDATE valid Rotation', function(t){ 52 | 53 | t.deepEqual(v.Rotation(40), {z:40}); 54 | t.deepEqual(v.Rotation({x:20, y:10, z:90}), {x:20, y:10, z:90}); 55 | 56 | t.end(); 57 | 58 | }); 59 | 60 | // Scale validation 61 | 62 | test('VALIDATE invalid Scale', function(t){ 63 | 64 | t.error(v.Scale('scale'), 'error'); 65 | t.end(); 66 | 67 | }); 68 | 69 | test('VALIDATE valid Scale', function(t){ 70 | 71 | t.deepEqual(v.Scale(22), {x:22, y:22, z:22}); 72 | t.deepEqual(v.Scale({x:20, y:10, z:90}), {x:20, y:10, z:90}); 73 | 74 | t.end(); 75 | 76 | }); 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /tests/unit/object.test.js: -------------------------------------------------------------------------------- 1 | function objectTest(instance){ 2 | 3 | var mesh = new THREE.Mesh(); 4 | var group 5 | 6 | test('OBJECT _makeGroup from one object', function(t){ 7 | group = instance.objects._makeGroup(mesh, {foo: true}); 8 | 9 | t.equal(group.userData.foo, true); 10 | t.equal(group.type, 'Group'); 11 | t.equal(group.children.length, 1); 12 | t.end(); 13 | 14 | }); 15 | 16 | test('OBJECT _makeGroup from multiple objects', function(t){ 17 | var mesh2 = new THREE.Mesh(); 18 | group = instance.objects._makeGroup([mesh, mesh2], {foo: false}); 19 | 20 | t.equal(group.userData.foo, false); 21 | t.equal(group.type, 'Group'); 22 | t.equal(group.children.length, 2); 23 | t.end(); 24 | 25 | }) 26 | 27 | test('OBJECT _addMethods static', function(t){ 28 | 29 | group = instance.objects._makeGroup(mesh, {}); 30 | var addedMethods = instance.objects._addMethods(group, true); 31 | 32 | t.equal(addedMethods.setCoords, undefined); 33 | t.equal(addedMethods.type, 'Group'); 34 | t.end(); 35 | 36 | }) 37 | 38 | test('OBJECT _addMethods dynamic', function(t){ 39 | 40 | var sphere = instance.sphere({units: 'meters'}); 41 | 42 | t.equal(sphere.type, 'Group'); 43 | t.equal(typeof sphere.followPath, 'function'); 44 | t.deepEqual(sphere.coordinates, [0,0,0]); 45 | t.end(); 46 | 47 | }) 48 | 49 | test('OBJECT setCoords updates both position and scale', function(t){ 50 | 51 | var sphere = instance.sphere({units: 'meters'}); 52 | var newPosition = [0,-20]; 53 | 54 | sphere.setCoords(newPosition) 55 | var scaleFactor = instance.projectedUnitsPerMeter(newPosition[1]); 56 | 57 | vector3Equals(t, sphere.position, instance.projectToWorld(newPosition)); 58 | t.equal(sphere.scale.x, scaleFactor); 59 | t.deepEqual(sphere.coordinates, newPosition) 60 | t.end(); 61 | 62 | }) 63 | 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/objects/extrusion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jscastro / https://github.com/jscastro76 3 | */ 4 | const Objects = require('./objects.js'); 5 | const utils = require("../utils/utils.js"); 6 | const THREE = require("../three.js"); 7 | const Object3D = require('./Object3D.js'); 8 | 9 | /** 10 | * 11 | * @param {any} opt must fit the default defined in Objects.prototype._defaults.extrusion 12 | * @param {arr} opt.coordinates could receive a feature.geometry.coordinates 13 | */ 14 | function extrusion(opt) { 15 | 16 | opt = utils._validate(opt, Objects.prototype._defaults.extrusion); 17 | let shape = extrusion.prototype.buildShape(opt.coordinates); 18 | let geometry = extrusion.prototype.buildGeometry(shape, opt.geometryOptions); 19 | let mesh = new THREE.Mesh(geometry, opt.materials); 20 | opt.obj = mesh; 21 | //[jscastro] we convert it in Object3D to add methods, bounding box, model, tooltip... 22 | return new Object3D(opt); 23 | 24 | } 25 | 26 | extrusion.prototype = { 27 | 28 | buildShape: function (coords) { 29 | if (coords[0] instanceof (THREE.Vector2 || THREE.Vector3)) return new THREE.Shape(coords); 30 | let shape = new THREE.Shape(); 31 | for (let i = 0; i < coords.length; i++) { 32 | if (i === 0) { 33 | shape = new THREE.Shape(this.buildPoints(coords[0], coords[0])); 34 | } else { 35 | shape.holes.push(new THREE.Path(this.buildPoints(coords[i], coords[0]))); 36 | } 37 | } 38 | return shape; 39 | }, 40 | 41 | buildPoints: function (coords, initCoords) { 42 | const points = []; 43 | let init = utils.projectToWorld([initCoords[0][0], initCoords[0][1], 0]); 44 | for (let i = 0; i < coords.length; i++) { 45 | let pos = utils.projectToWorld([coords[i][0], coords[i][1], 0]); 46 | points.push(new THREE.Vector2(utils.toDecimal((pos.x - init.x), 9), utils.toDecimal((pos.y - init.y), 9))); 47 | } 48 | return points; 49 | }, 50 | 51 | buildGeometry: function (shape, settings) { 52 | let geometry = new THREE.ExtrudeBufferGeometry(shape, settings); 53 | geometry.computeBoundingBox(); 54 | return geometry; 55 | } 56 | 57 | } 58 | 59 | module.exports = exports = extrusion; -------------------------------------------------------------------------------- /src/objects/LabelRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author jscastro / https://github.com/jscastro76 3 | */ 4 | 5 | const THREE = require("./CSS2DRenderer.js"); 6 | 7 | function LabelRenderer(map) { 8 | 9 | this.map = map; 10 | 11 | this.renderer = new THREE.CSS2DRenderer(); 12 | 13 | this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight); 14 | this.renderer.domElement.style.position = 'absolute'; 15 | this.renderer.domElement.id = 'labelCanvas'; //TODO: this value must come by parameter 16 | this.renderer.domElement.style.top = 0; 17 | this.renderer.domElement.style.zIndex = "0"; 18 | this.map.getCanvasContainer().appendChild(this.renderer.domElement); 19 | 20 | this.scene, this.camera; 21 | 22 | this.dispose = function () { 23 | this.map.getCanvasContainer().removeChild(this.renderer.domElement) 24 | this.renderer.domElement.remove(); 25 | this.renderer = {}; 26 | } 27 | 28 | this.setSize = function (width, height) { 29 | this.renderer.setSize(width, height); 30 | } 31 | 32 | this.map.on('resize', function () { 33 | this.renderer.setSize(this.map.getCanvas().clientWidth, this.map.getCanvas().clientHeight); 34 | }.bind(this)); 35 | 36 | this.state = { 37 | reset: function () { 38 | //TODO: Implement a good state reset, check out what is made in WebGlRenderer 39 | } 40 | } 41 | 42 | this.render = async function (scene, camera) { 43 | this.scene = scene; 44 | this.camera = camera; 45 | return new Promise((resolve) => { resolve(this.renderer.render(scene, camera)) }); 46 | } 47 | 48 | //[jscastro] method to toggle Layer visibility 49 | this.toggleLabels = async function (layerId, visible) { 50 | return new Promise((resolve) => { 51 | resolve(this.setVisibility(layerId, visible, this.scene, this.camera, this.renderer)); 52 | }) 53 | }; 54 | 55 | //[jscastro] method to set visibility 56 | this.setVisibility = function (layerId, visible, scene, camera, renderer) { 57 | var cache = this.renderer.cacheList; 58 | cache.forEach(function (l) { 59 | if (l.visible != visible && l.layer === layerId) { 60 | if ((visible && l.alwaysVisible) || !visible) { 61 | l.visible = visible; 62 | renderer.renderObject(l, scene, camera); 63 | } 64 | } 65 | }); 66 | }; 67 | 68 | } 69 | 70 | module.exports = exports = LabelRenderer; -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var http = require('http'); 3 | var url = require('url'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var baseDirectory = __dirname; 7 | var port = 8080 || process.env.PORT || 1337; 8 | var counter = 0; 9 | 10 | http.createServer(function (request, response) { 11 | try { 12 | var requestUrl = url.parse(request.url); 13 | // need to use path.normalize so people can't access directories underneath baseDirectory 14 | var fsPath = baseDirectory + path.normalize(requestUrl.pathname); 15 | 16 | var ext = path.extname(fsPath) 17 | var validExtensions = { 18 | ".html": "text/html", 19 | ".js": "application/javascript", 20 | ".json": "application/json", 21 | ".geojson": "application/json", 22 | ".bin": "application/octet-stream", 23 | ".css": "text/css", 24 | ".txt": "text/plain", 25 | ".bmp": "image/bmp", 26 | ".jpg": "image/jpeg", 27 | ".gif": "image/gif", 28 | ".png": "image/png", 29 | ".ico": "image/x-icon", 30 | ".dae": "application/vnd.oipf.dae.svg+xml", 31 | ".pbf": "application/octet-stream", 32 | ".mtl": "model/mtl", 33 | ".obj": "model/obj", 34 | ".glb": "model/gltf-binary", 35 | ".gltf": "model/gltf+json", 36 | ".fbx": "application/octet-stream", 37 | ".ttf": "application/octet-stream", 38 | ".woff": "font/woff", 39 | ".woff2": "font/woff2", 40 | 41 | }; 42 | 43 | var isValidExt = validExtensions[ext]; 44 | 45 | var fileStream = fs.createReadStream(fsPath); 46 | fileStream.pipe(response); 47 | fileStream.on('open', function () { 48 | response.setHeader("Content-Type", validExtensions[ext]); 49 | response.writeHead(200); 50 | }); 51 | fileStream.on('error', function (e) { 52 | response.writeHead(404); // assume the file doesn't exist 53 | response.end(); 54 | }); 55 | } catch (e) { 56 | response.writeHead(500); 57 | response.end(); // end the response so browsers don't hang 58 | console.log(e.stack); 59 | } 60 | 61 | }).listen(port); 62 | 63 | -------------------------------------------------------------------------------- /tests/unit/material.test.js: -------------------------------------------------------------------------------- 1 | function materialTest(instance){ 2 | 3 | var material; 4 | 5 | test('MATERIAL default type, color, opacity', function(t) { 6 | material = instance.material(); 7 | t.equal(material.type, 'MeshBasicMaterial'); 8 | t.deepEqual(material.color, { r: 0, g: 0, b: 0 }); 9 | t.equal(material.opacity, 1); 10 | t.end(); 11 | }); 12 | 13 | test('MATERIAL custom type', function(t) { 14 | material = instance.material({material:'MeshPhysicalMaterial'}); 15 | t.equal(material.opacity, 1); 16 | t.equal(material.type, 'MeshPhysicalMaterial'); 17 | t.deepEqual(material.color, { r: 0, g: 0, b: 0 }); 18 | t.end(); 19 | }); 20 | 21 | test('MATERIAL custom color', function(t) { 22 | material = instance.material({color:'red'}); 23 | t.equal(material.opacity, 1); 24 | t.equal(material.type, 'MeshBasicMaterial'); 25 | t.deepEqual(material.color, { r: 1, g: 0, b: 0 }); 26 | t.end(); 27 | }); 28 | 29 | test('MATERIAL custom opacity', function(t) { 30 | material = instance.material({opacity:0.5}); 31 | t.equal(material.opacity, 0.5); 32 | t.equal(material.type, 'MeshBasicMaterial'); 33 | t.deepEqual(material.color, { r: 0, g: 0, b: 0 }); 34 | t.end(); 35 | }); 36 | 37 | test('MATERIAL custom color, opacity, type', function(t) { 38 | allCustom = instance.material({ 39 | material: 'MeshBasicMaterial', 40 | opacity: 0.5, 41 | color: 'blue' 42 | }); 43 | t.equal(allCustom.opacity, 0.5); 44 | t.equal(allCustom.type, 'MeshBasicMaterial'); 45 | t.deepEqual(allCustom.color, { r: 0, g: 0, b: 1 }); 46 | t.end(); 47 | }); 48 | 49 | test('MATERIAL when THREE.Material provided, other material params ignored except opacity', function(t) { 50 | threeMaterial = instance.material({ 51 | material: new THREE.MeshBasicMaterial({color: 'cyan'}), 52 | opacity: 0.5, 53 | color: 'blue' 54 | }); 55 | 56 | t.equal(threeMaterial.opacity, 0.5); 57 | t.equal(threeMaterial.type, 'MeshBasicMaterial'); 58 | t.deepEqual(threeMaterial.color, { r: 0, g: 1, b: 1 }); 59 | t.end(); 60 | }); 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /examples/01-basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sphere Example 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 | 24 |
25 | 26 | 100 | -------------------------------------------------------------------------------- /examples/css/free-v4-font-face-min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome Free 5.12.1 by @fontawesome - https://fontawesome.com 3 | * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) 4 | */ 5 | @font-face { 6 | font-family: "FontAwesome"; 7 | src: url(../webfonts/free-fa-solid-900.eot),url(../webfonts/free-fa-solid-900.eot?#iefix) format("embedded-opentype"),url(../webfonts/free-fa-solid-900.woff2) format("woff2"),url(../webfonts/free-fa-solid-900.woff) format("woff"),url(../webfonts/free-fa-solid-900.ttf) format("truetype"),url(../webfonts/free-fa-solid-900.svg#fontawesome) format("svg") 8 | } 9 | 10 | @font-face { 11 | font-family: "FontAwesome"; 12 | src: url(../webfonts/free-fa-brands-400.eot),url(../webfonts/free-fa-brands-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/free-fa-brands-400.woff2) format("woff2"),url(../webfonts/free-fa-brands-400.woff) format("woff"),url(../webfonts/free-fa-brands-400.ttf) format("truetype"),url(../webfonts/free-fa-brands-400.svg#fontawesome) format("svg") 13 | } 14 | 15 | @font-face { 16 | font-family: "FontAwesome"; 17 | src: url(../webfonts/free-fa-regular-400.eot),url(../webfonts/free-fa-regular-400.eot?#iefix) format("embedded-opentype"),url(../webfonts/free-fa-regular-400.woff2) format("woff2"),url(../webfonts/free-fa-regular-400.woff) format("woff"),url(../webfonts/free-fa-regular-400.ttf) format("truetype"),url(../webfonts/free-fa-regular-400.svg#fontawesome) format("svg"); 18 | unicode-range: U+f004-f005,U+f007,U+f017,U+f022,U+f024,U+f02e,U+f03e,U+f044,U+f057-f059,U+f06e,U+f070,U+f075,U+f07b-f07c,U+f080,U+f086,U+f089,U+f094,U+f09d,U+f0a0,U+f0a4-f0a7,U+f0c5,U+f0c7-f0c8,U+f0e0,U+f0eb,U+f0f3,U+f0f8,U+f0fe,U+f111,U+f118-f11a,U+f11c,U+f133,U+f144,U+f146,U+f14a,U+f14d-f14e,U+f150-f152,U+f15b-f15c,U+f164-f165,U+f185-f186,U+f191-f192,U+f1ad,U+f1c1-f1c9,U+f1cd,U+f1d8,U+f1e3,U+f1ea,U+f1f6,U+f1f9,U+f20a,U+f247-f249,U+f24d,U+f254-f25b,U+f25d,U+f271-f274,U+f279,U+f28b,U+f28d,U+f2b5-f2b6,U+f2b9,U+f2bb,U+f2bd,U+f2c1-f2c2,U+f2d0,U+f2d2,U+f2dc,U+f2ed,U+f3a5,U+f3d1,U+f410 19 | } 20 | 21 | @font-face { 22 | font-family: "FontAwesome"; 23 | src: url(../webfonts/free-fa-v4deprecations.eot),url(../webfonts/free-fa-v4deprecations.eot?#iefix) format("embedded-opentype"),url(../webfonts/free-fa-v4deprecations.woff2) format("woff2"),url(../webfonts/free-fa-v4deprecations.woff) format("woff"),url(../webfonts/free-fa-v4deprecations.ttf) format("truetype"),url(../webfonts/free-fa-v4deprecations.svg#fontawesome) format("svg"); 24 | unicode-range: U+f003,U+f006,U+f014,U+f016,U+f01a-f01b,U+f01d,U+f040,U+f045-f047,U+f05c-f05d,U+f07d-f07e,U+f087-f088,U+f08a-f08b,U+f08e,U+f090,U+f096-f097,U+f0a2,U+f0e4-f0e6,U+f0ec-f0ee,U+f0f5-f0f7,U+f10c,U+f112,U+f114-f115,U+f11d,U+f123,U+f132,U+f145,U+f147-f149,U+f14c,U+f166,U+f16a,U+f172,U+f175-f178,U+f18e,U+f190,U+f196,U+f1b1,U+f1d9,U+f1db,U+f1f7,U+f20c,U+f219,U+f230,U+f24a,U+f250,U+f278,U+f27b,U+f283,U+f28c,U+f28e,U+f29b-f29c,U+f2b7,U+f2ba,U+f2bc,U+f2be,U+f2c0,U+f2c3,U+f2d3-f2d4 25 | } 26 | -------------------------------------------------------------------------------- /examples/06-object3d.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threebox Object3D Example 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 |
24 | 25 | 100 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | // Type validator 2 | 3 | function Validate(){ 4 | 5 | }; 6 | 7 | Validate.prototype = { 8 | 9 | Coords: function(input) { 10 | 11 | if (input.constructor !== Array) { 12 | console.error("Coords must be an array") 13 | return 14 | } 15 | 16 | if (input.length < 2) { 17 | console.error("Coords length must be at least 2") 18 | return 19 | } 20 | 21 | for (const member of input) { 22 | if (member.constructor !== Number) { 23 | console.error("Coords values must be numbers") 24 | return 25 | } 26 | } 27 | 28 | if (Math.abs(input[1]) > 90) { 29 | console.error("Latitude must be between -90 and 90") 30 | return 31 | } 32 | 33 | return input 34 | }, 35 | 36 | Line: function(input) { 37 | 38 | var scope = this; 39 | 40 | if (input.constructor !== Array) { 41 | console.error("Line must be an array") 42 | return 43 | } 44 | 45 | for (const coord of input){ 46 | if (!scope.Coords(coord)) { 47 | console.error("Each coordinate in a line must be a valid Coords type") 48 | return 49 | } 50 | 51 | } 52 | 53 | return input 54 | }, 55 | 56 | Rotation: function(input) { 57 | 58 | if (input.constructor === Number) input = {z: input} 59 | 60 | else if (input.constructor === Object) { 61 | 62 | for (const key of Object.keys(input)){ 63 | 64 | if (!['x', 'y', 'z'].includes(key)) { 65 | console.error('Rotation parameters must be x, y, or z') 66 | return 67 | } 68 | if (input[key].constructor !== Number) { 69 | console.error('Individual rotation values must be numbers') 70 | return 71 | } 72 | } 73 | } 74 | 75 | else { 76 | console.error('Rotation must be an object or a number') 77 | return 78 | } 79 | 80 | return input 81 | }, 82 | 83 | Scale: function(input) { 84 | 85 | if (input.constructor === Number) { 86 | input = {x:input, y:input, z: input} 87 | } 88 | 89 | else if (input.constructor === Object) { 90 | 91 | for (const key of Object.keys(input)){ 92 | 93 | if (!['x', 'y', 'z'].includes(key)) { 94 | console.error('Scale parameters must be x, y, or z') 95 | return 96 | } 97 | if (input[key].constructor !== Number) { 98 | console.error('Individual scale values must be numbers') 99 | return 100 | } 101 | } 102 | } 103 | 104 | else { 105 | console.error('Scale must be an object or a number') 106 | return 107 | } 108 | 109 | return input 110 | } 111 | 112 | } 113 | 114 | 115 | module.exports = exports = Validate; -------------------------------------------------------------------------------- /examples/02-line.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Line Example 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 | 24 |
25 | 26 | 123 | -------------------------------------------------------------------------------- /examples/03-tube.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Tube Example 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 |
24 | 25 | 121 | -------------------------------------------------------------------------------- /tests/threebox-tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threebox tests 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 27 | 28 | 29 | Open the console to see test results 30 |
31 | 32 | 99 | -------------------------------------------------------------------------------- /examples/04-mercator.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Mercator projection 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 |
24 | 25 | 125 | -------------------------------------------------------------------------------- /examples/10-stylechange.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threebox change map style for Eiffel Tower 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 29 | 30 | 31 |
32 | 44 | 135 | 136 | -------------------------------------------------------------------------------- /examples/17-azuremaps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Azure Maps Sample 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 31 | 32 | 33 |
34 | 35 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/21-multifloor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threebox alignment Test 5 | 6 | 7 | 8 | 9 | 10 | 23 | 24 | 25 |
26 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /src/objects/loadObj.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author peterqliu / https://github.com/peterqliu 3 | * @author jscastro / https://github.com/jscastro76 4 | */ 5 | const utils = require("../utils/utils.js"); 6 | const Objects = require('./objects.js'); 7 | const OBJLoader = require("./loaders/OBJLoader.js"); 8 | const MTLLoader = require("./loaders/MTLLoader.js"); 9 | const FBXLoader = require("./loaders/FBXLoader.js"); 10 | const GLTFLoader = require("./loaders/GLTFLoader.js"); 11 | const ColladaLoader = require("./loaders/ColladaLoader.js"); 12 | const objLoader = new OBJLoader(); 13 | const materialLoader = new MTLLoader(); 14 | const gltfLoader = new GLTFLoader(); 15 | const fbxLoader = new FBXLoader(); 16 | const daeLoader = new ColladaLoader(); 17 | 18 | function loadObj(options, cb, promise) { 19 | 20 | if (options === undefined) return console.error("Invalid options provided to loadObj()"); 21 | options = utils._validate(options, Objects.prototype._defaults.loadObj); 22 | 23 | let loader; 24 | if (!options.type) { options.type = 'mtl'; }; 25 | //[jscastro] support other models 26 | switch (options.type) { 27 | case "mtl": 28 | // TODO: Support formats other than OBJ/MTL 29 | loader = objLoader; 30 | break; 31 | case "gltf": 32 | case "glb": 33 | // [jscastro] Support for GLTF/GLB 34 | loader = gltfLoader; 35 | break; 36 | case "fbx": 37 | loader = fbxLoader; 38 | break; 39 | case "dae": 40 | loader = daeLoader; 41 | break; 42 | } 43 | 44 | materialLoader.withCredentials = options.withCredentials; 45 | materialLoader.load(options.mtl, loadObject, () => (null), error => { 46 | console.warn("No material file found " + error.stack); 47 | }); 48 | 49 | function loadObject(materials) { 50 | 51 | if (materials && options.type == "mtl") { 52 | materials.preload(); 53 | loader.setMaterials(materials); 54 | } 55 | 56 | loader.withCredentials = options.withCredentials; 57 | loader.load(options.obj, obj => { 58 | 59 | //[jscastro] MTL/GLTF/FBX models have a different structure 60 | let animations = []; 61 | switch (options.type) { 62 | case "mtl": 63 | obj = obj.children[0]; 64 | break; 65 | case "gltf": 66 | case "glb": 67 | case "dae": 68 | animations = obj.animations; 69 | obj = obj.scene; 70 | break; 71 | case "fbx": 72 | animations = obj.animations; 73 | break; 74 | } 75 | obj.animations = animations; 76 | // [jscastro] options.rotation was wrongly used 77 | const r = utils.types.rotation(options.rotation, [0, 0, 0]); 78 | const s = utils.types.scale(options.scale, [1, 1, 1]); 79 | obj.rotation.set(r[0], r[1], r[2]); 80 | obj.scale.set(s[0], s[1], s[2]); 81 | // [jscastro] normalize specular/metalness/shininess from meshes in FBX and GLB model as it would need 5 lights to illuminate them properly 82 | if (options.normalize) { normalizeSpecular(obj); } 83 | obj.name = "model"; 84 | let userScaleGroup = Objects.prototype._makeGroup(obj, options); 85 | Objects.prototype._addMethods(userScaleGroup); 86 | //[jscastro] calculate automatically the pivotal center of the object 87 | userScaleGroup.setAnchor(options.anchor); 88 | //[jscastro] override the center calculated if the object has adjustments 89 | userScaleGroup.setCenter(options.adjustment); 90 | //[jscastro] if the object is excluded from raycasting 91 | userScaleGroup.raycasted = options.raycasted; 92 | //[jscastro] return to cache 93 | promise(userScaleGroup); 94 | //[jscastro] then return to the client-side callback 95 | cb(userScaleGroup); 96 | //[jscastro] apply the fixed zoom scale if needed 97 | userScaleGroup.setFixedZoom(options.mapScale); 98 | //[jscastro] initialize the default animation to avoid issues with skeleton position 99 | userScaleGroup.idle(); 100 | 101 | }, () => (null), error => { 102 | console.error("Could not load model file: " + options.obj + " \n " + error.stack); 103 | promise("Error loading the model"); 104 | }); 105 | 106 | }; 107 | 108 | //[jscastro] some FBX/GLTF models have too much specular effects for mapbox 109 | function normalizeSpecular(model) { 110 | model.traverse(function (c) { 111 | 112 | if (c.isMesh) { 113 | //c.castShadow = true; 114 | let specularColor; 115 | if (c.material.type == 'MeshStandardMaterial') { 116 | 117 | if (c.material.metalness) { c.material.metalness *= 0.1; } 118 | if (c.material.glossiness) { c.material.glossiness *= 0.25; } 119 | specularColor = new THREE.Color(12, 12, 12); 120 | 121 | } else if (c.material.type == 'MeshPhongMaterial') { 122 | c.material.shininess = 0.1; 123 | specularColor = new THREE.Color(20, 20, 20); 124 | } 125 | if (c.material.specular && c.material.specular.isColor) { 126 | c.material.specular = specularColor; 127 | } 128 | //c.material.needsUpdate = true; 129 | 130 | } 131 | 132 | }); 133 | } 134 | 135 | } 136 | 137 | module.exports = exports = loadObj; -------------------------------------------------------------------------------- /examples/12-add3dmodel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Add a 3D model with Threebox 5 | 6 | 7 | 8 | 9 | 10 | 49 | 50 | 51 |
52 | 53 |
54 | 55 | 158 | 159 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | v.2.0.1 - v.2.2.6 2 | MIT License 3 | Copyright (c) 2020 Jesus Serrano 4 | 5 | v.0.3.0 6 | MIT License 7 | Copyright (c) 2017 Peter Liu 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | --------------------------------------------------------------------------------------------- 28 | SunCalc (c) 2011-2015, Vladimir Agafonkin 29 | Copyright (c) 2014, Vladimir Agafonkin 30 | All rights reserved. 31 | 32 | Redistribution and use in source and binary forms, with or without modification, are 33 | permitted provided that the following conditions are met: 34 | 35 | 1. Redistributions of source code must retain the above copyright notice, this list of 36 | conditions and the following disclaimer. 37 | 38 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 39 | of conditions and the following disclaimer in the documentation and/or other materials 40 | provided with the distribution. 41 | 42 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 43 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 44 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 45 | COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 46 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 47 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 48 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 49 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 50 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 51 | 52 | --------------------------------------------------------------------------------------------- 53 | 3D models attributions 54 | 55 | - Eiffel Tower 56 | - Creative Commons License attribution: Eiffel Tower model by https://www.cgtrader.com/lefabshop 57 | from https://www.cgtrader.com/items/108594/download-page 58 | 59 | - Elephant 60 | - Creative Commons NonCommercial License attribution: Asian Elephant low poly model by https://sketchfab.com/jeremielouvetz 61 | from https://sketchfab.com/3d-models/asian-elephant-2aeeb8958bc64240962b093705abffdf 62 | 63 | - Liberty Statue 64 | - Creative Commons License attribution: Liberty statue model by https://sketchfab.com/hellolucy2 65 | from https://sketchfab.com/3d-models/ellis-island-3cd765a23c5c4c7087acd00624d30590 66 | 67 | - Plane 68 | - Creative Commons License attribution: Plane model by https://sketchfab.com/ideehochzwei 69 | from https://sketchfab.com/3d-models/plane-aa001f5a88f64b16b98356c042f2d5f3 70 | 71 | - Radar 72 | - Attribution, no License specified: Model by https://github.com/nasa/ 73 | from https://nasa3d.arc.nasa.gov/detail/jpl-vtad-dsn34 74 | 75 | - Soldier 76 | - Attribution: Soldier animated model by T. Choonyung at https://www.mixamo.com 77 | from https://www.mixamo.com/#/?page=1&query=vanguard&type=Character 78 | 79 | - Space Needle 80 | - Creative Commons License attribution: Space Needle model by https://sketchfab.com/microsoft 81 | from https://sketchfab.com/3d-models/space-needle-1d1325bc1ad745dd9eb34fc76e8f6e87 82 | 83 | - Triceratops 84 | - Creative Commons NonCommercial License attribution: Dino low poly model by https://sketchfab.com/Blender_Fox1234 85 | from https://sketchfab.com/3d-models/triceratops-lowpoly-76beb95d0b5b41a3aae0d07c3eae99fa 86 | 87 | - Truck & Car 88 | - Royalty Free License: Vehicles by https://www.cgtrader.com/antonmoek 89 | from https://www.cgtrader.com/free-3d-models/car/concept/cartoon-low-poly-city-cars-pack 90 | 91 | - Windmill 92 | - Creative Commons License attribution: Windmill animated model by https://sketchfab.com/data3anshow 93 | from https://sketchfab.com/3d-models/windmill-animated-6ce5667e8d5c47068ea13196036efd52 94 | 95 | - Glacier d'Argentiere 96 | - Attribution, no License specified: Glacier model by https://github.com/jbbarre 97 | from https://github.com/jbbarre/glacierargentiere/blob/main/data/glacier_wgs.gltf -------------------------------------------------------------------------------- /examples/18-extrusions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threbox extrusions sample 4 | 5 | 6 | 7 | 8 | 9 | 10 | 22 | 23 | 24 |
25 | 142 | -------------------------------------------------------------------------------- /examples/05-logistics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Animated truck 4 | 5 | 6 | 7 | 8 | 9 | 10 | 27 | 28 | 29 |
30 |
Click on the map to drive the truck there
31 | 179 | -------------------------------------------------------------------------------- /examples/08-3dbuildings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threebox display buildings in 3D with auto tooltips 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 178 | 179 | -------------------------------------------------------------------------------- /examples/14-buildingshadow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threebox building Sun light and shadows 5 | 6 | 7 | 8 | 9 | 10 | 49 | 50 | 51 |
52 | 53 |
54 | 55 | 187 | 188 | -------------------------------------------------------------------------------- /examples/07-alignmentTest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threebox alignment Test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 24 | 25 | 26 |
27 |
28 |
29 | This demo shows camera alignment between fill-extrusion layer and a 3DObject 30 |
31 |
32 |
33 | 180 | 181 | -------------------------------------------------------------------------------- /examples/css/fontawesome.js: -------------------------------------------------------------------------------- 1 | window.FontAwesomeKitConfig = { "asyncLoading": { "enabled": false }, "autoA11y": { "enabled": true }, "baseUrl": "https://kit-free.fontawesome.com", "detectConflictsUntil": null, "license": "free", "method": "css", "minify": { "enabled": true }, "v4FontFaceShim": { "enabled": true }, "v4shim": { "enabled": true }, "version": "latest" }; 2 | !function () { function r(e) { var t, n = [], i = document, o = i.documentElement.doScroll, r = "DOMContentLoaded", a = (o ? /^loaded|^c/ : /^loaded|^i|^c/).test(i.readyState); a || i.addEventListener(r, t = function () { for (i.removeEventListener(r, t), a = 1; t = n.shift();)t() }), a ? setTimeout(e, 0) : n.push(e) } !function () { if (!(void 0 === window.Element || "classList" in document.documentElement)) { var e, t, n, i = Array.prototype, o = i.push, r = i.splice, a = i.join; d.prototype = { add: function (e) { this.contains(e) || (o.call(this, e), this.el.className = this.toString()) }, contains: function (e) { return -1 != this.el.className.indexOf(e) }, item: function (e) { return this[e] || null }, remove: function (e) { if (this.contains(e)) { for (var t = 0; t < this.length && this[t] != e; t++); r.call(this, t, 1), this.el.className = this.toString() } }, toString: function () { return a.call(this, " ") }, toggle: function (e) { return this.contains(e) ? this.remove(e) : this.add(e), this.contains(e) } }, window.DOMTokenList = d, e = Element.prototype, t = "classList", n = function () { return new d(this) }, Object.defineProperty ? Object.defineProperty(e, t, { get: n }) : e.__defineGetter__(t, n) } function d(e) { for (var t = (this.el = e).className.replace(/^\s+|\s+$/g, "").split(/\s+/), n = 0; n < t.length; n++)o.call(this, t[n]) } }(); function a(e) { var t, n, i, o; prefixesArray = e || ["fa"], prefixesSelectorString = "." + Array.prototype.join.call(e, ",."), t = document.querySelectorAll(prefixesSelectorString), Array.prototype.forEach.call(t, function (e) { n = e.getAttribute("title"), e.setAttribute("aria-hidden", "true"), i = !e.nextElementSibling || !e.nextElementSibling.classList.contains("sr-only"), n && i && ((o = document.createElement("span")).innerHTML = n, o.classList.add("sr-only"), e.parentNode.insertBefore(o, e.nextSibling)) }) } var d = function (e, t) { var n = document.createElement("link"); n.href = e, n.media = "all", n.rel = "stylesheet", t && t.detectingConflicts && t.detectionIgnoreAttr && n.setAttributeNode(document.createAttribute(t.detectionIgnoreAttr)), document.getElementsByTagName("head")[0].appendChild(n) }, c = function (e, t) { !function (e, t) { var n, i = t && t.before || void 0, o = t && t.media || void 0, r = window.document, a = r.createElement("link"); if (t && t.detectingConflicts && t.detectionIgnoreAttr && a.setAttributeNode(document.createAttribute(t.detectionIgnoreAttr)), i) n = i; else { var d = (r.body || r.getElementsByTagName("head")[0]).childNodes; n = d[d.length - 1] } var c = r.styleSheets; a.rel = "stylesheet", a.href = e, a.media = "only x", function e(t) { if (r.body) return t(); setTimeout(function () { e(t) }) }(function () { n.parentNode.insertBefore(a, i ? n : n.nextSibling) }); var s = function (e) { for (var t = a.href, n = c.length; n--;)if (c[n].href === t) return e(); setTimeout(function () { s(e) }) }; function l() { a.addEventListener && a.removeEventListener("load", l), a.media = o || "all" } a.addEventListener && a.addEventListener("load", l), (a.onloadcssdefined = s)(l) }(e, t) }, e = function (e, t, n) { var i = t && void 0 !== t.autoFetchSvg ? t.autoFetchSvg : void 0, o = t && void 0 !== t.async ? t.async : void 0, r = t && void 0 !== t.autoA11y ? t.autoA11y : void 0, a = document.createElement("script"), d = document.scripts[0]; a.src = e, void 0 !== r && a.setAttribute("data-auto-a11y", r ? "true" : "false"), i && (a.setAttributeNode(document.createAttribute("data-auto-fetch-svg")), a.setAttribute("data-fetch-svg-from", t.fetchSvgFrom)), o && a.setAttributeNode(document.createAttribute("defer")), n && n.detectingConflicts && n.detectionIgnoreAttr && a.setAttributeNode(document.createAttribute(n.detectionIgnoreAttr)), d.parentNode.appendChild(a) }; function s(e, t) { var n = t && t.addOn || "", i = t && t.baseFilename || e.license + n, o = t && t.minify ? ".min" : "", r = t && t.fileSuffix || e.method, a = t && t.subdir || e.method; return e.baseUrl + "/releases/" + ("latest" === e.version ? "latest" : "v".concat(e.version)) + "/" + a + "/" + i + o + "." + r } var t, n, i, o, l; try { if (window.FontAwesomeKitConfig) { var u, f = window.FontAwesomeKitConfig, m = { detectingConflicts: f.detectConflictsUntil && new Date <= new Date(f.detectConflictsUntil), detectionIgnoreAttr: "data-fa-detection-ignore", detectionTimeoutAttr: "data-fa-detection-timeout", detectionTimeout: null }; "js" === f.method && (o = m, l = { async: (i = f).asyncLoading.enabled, autoA11y: i.autoA11y.enabled }, "pro" === i.license && (l.autoFetchSvg = !0, l.fetchSvgFrom = i.baseUrl + "/releases/" + ("latest" === i.version ? "latest" : "v".concat(i.version)) + "/svgs"), i.v4shim.enabled && e(s(i, { addOn: "-v4-shims", minify: i.minify.enabled })), e(s(i, { minify: i.minify.enabled }), l, o)), "css" === f.method && function (e, t) { var n, i = a.bind(a, ["fa", "fab", "fas", "far", "fal", "fad"]); e.autoA11y.enabled && (r(i), n = i, "undefined" != typeof MutationObserver && new MutationObserver(n).observe(document, { childList: !0, subtree: !0 })), e.v4shim.enabled && (e.license, e.asyncLoading.enabled ? c(s(e, { addOn: "-v4-shims", minify: e.minify.enabled }), t) : d(s(e, { addOn: "-v4-shims", minify: e.minify.enabled }), t)); e.v4FontFaceShim.enabled && (e.asyncLoading.enabled ? c(s(e, { addOn: "-v4-font-face", minify: e.minify.enabled }), t) : d(s(e, { addOn: "-v4-font-face", minify: e.minify.enabled }), t)); var o = s(e, { minify: e.minify.enabled }); e.asyncLoading.enabled ? c(o, t) : d(o, t) }(f, m), m.detectingConflicts && ((u = document.currentScript.getAttribute(m.detectionTimeoutAttr)) && (m.detectionTimeout = u), document.currentScript.setAttributeNode(document.createAttribute(m.detectionIgnoreAttr)), t = f, n = m, r(function () { var e = document.createElement("script"); n && n.detectionIgnoreAttr && e.setAttributeNode(document.createAttribute(n.detectionIgnoreAttr)), n && n.detectionTimeoutAttr && n.detectionTimeout && e.setAttribute(n.detectionTimeoutAttr, n.detectionTimeout), e.src = s(t, { baseFilename: "conflict-detection", fileSuffix: "js", subdir: "js", minify: t.minify.enabled }), e.async = !0, document.body.appendChild(e) })) } } catch (e) { } }(); -------------------------------------------------------------------------------- /src/objects/CSS2DRenderer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | const THREE = require('../three.js'); 6 | 7 | (function () { 8 | 9 | class CSS2DObject extends THREE.Object3D { 10 | 11 | constructor(element) { 12 | 13 | super(); 14 | this.element = element || document.createElement('div'); 15 | this.element.style.position = 'absolute'; 16 | this.element.style.userSelect = 'none'; 17 | this.element.setAttribute('draggable', false); 18 | 19 | //[jscastro] some labels must be always visible 20 | this.alwaysVisible = false; 21 | 22 | //[jscastro] layer is needed to be rendered/hidden based on layer visibility 23 | Object.defineProperty(this, 'layer', { 24 | get() { return (this.parent && this.parent.parent ? this.parent.parent.layer : null) } 25 | }); 26 | 27 | //[jscastro] implement dispose 28 | this.dispose = function () { 29 | this.remove(); 30 | this.element = null; 31 | } 32 | //[jscastro] implement explicit method 33 | this.remove = function () { 34 | if (this.element instanceof Element && this.element.parentNode !== null) { 35 | this.element.parentNode.removeChild(this.element); 36 | } 37 | } 38 | 39 | this.addEventListener('removed', function () { 40 | 41 | this.remove(); 42 | 43 | }); 44 | 45 | } 46 | 47 | copy(source, recursive) { 48 | 49 | super.copy(source, recursive); 50 | this.element = source.element.cloneNode(true); 51 | return this; 52 | 53 | } 54 | 55 | } 56 | 57 | CSS2DObject.prototype.isCSS2DObject = true; // 58 | 59 | const _vector = new THREE.Vector3(); 60 | 61 | const _viewMatrix = new THREE.Matrix4(); 62 | 63 | const _viewProjectionMatrix = new THREE.Matrix4(); 64 | 65 | const _a = new THREE.Vector3(); 66 | 67 | const _b = new THREE.Vector3(); 68 | 69 | class CSS2DRenderer { 70 | 71 | constructor() { 72 | 73 | const _this = this; 74 | 75 | let _width, _height; 76 | 77 | let _widthHalf, _heightHalf; 78 | 79 | const cache = { 80 | objects: new WeakMap(), 81 | list: new Map() 82 | }; 83 | this.cacheList = cache.list; 84 | const domElement = document.createElement('div'); 85 | domElement.style.overflow = 'hidden'; 86 | this.domElement = domElement; 87 | 88 | this.getSize = function () { 89 | 90 | return { 91 | width: _width, 92 | height: _height 93 | }; 94 | 95 | }; 96 | 97 | this.render = function (scene, camera) { 98 | 99 | if (scene.autoUpdate === true) scene.updateMatrixWorld(); 100 | if (camera.parent === null) camera.updateMatrixWorld(); 101 | 102 | _viewMatrix.copy(camera.matrixWorldInverse); 103 | 104 | _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix); 105 | 106 | renderObject(scene, scene, camera); 107 | zOrder(scene); 108 | 109 | }; 110 | 111 | this.setSize = function (width, height) { 112 | 113 | _width = width; 114 | _height = height; 115 | _widthHalf = _width / 2; 116 | _heightHalf = _height / 2; 117 | domElement.style.width = width + 'px'; 118 | domElement.style.height = height + 'px'; 119 | 120 | }; 121 | 122 | function renderObject(object, scene, camera) { 123 | 124 | if (object.isCSS2DObject) { 125 | 126 | //[jscastro] optimize performance and don't update and remove the labels that are not visible 127 | if (!object.visible) { 128 | cache.objects.delete({ key: object.uuid }); 129 | cache.list.delete(object.uuid); 130 | object.remove(); 131 | } 132 | else { 133 | 134 | object.onBeforeRender(_this, scene, camera); 135 | 136 | _vector.setFromMatrixPosition(object.matrixWorld); 137 | 138 | _vector.applyMatrix4(_viewProjectionMatrix); 139 | 140 | const element = object.element; 141 | var style; 142 | if (/apple/i.test(navigator.vendor)) { 143 | 144 | // https://github.com/mrdoob/three.js/issues/21415 145 | style = 'translate(-50%,-50%) translate(' + Math.round(_vector.x * _widthHalf + _widthHalf) + 'px,' + Math.round(- _vector.y * _heightHalf + _heightHalf) + 'px)'; 146 | 147 | } else { 148 | 149 | style = 'translate(-50%,-50%) translate(' + (_vector.x * _widthHalf + _widthHalf) + 'px,' + (- _vector.y * _heightHalf + _heightHalf) + 'px)'; 150 | 151 | } 152 | 153 | element.style.WebkitTransform = style; 154 | element.style.MozTransform = style; 155 | element.style.oTransform = style; 156 | element.style.transform = style; 157 | 158 | element.style.display = object.visible && _vector.z >= - 1 && _vector.z <= 1 ? '' : 'none'; 159 | 160 | const objectData = { 161 | distanceToCameraSquared: getDistanceToSquared(camera, object) 162 | }; 163 | 164 | cache.objects.set({ key: object.uuid }, objectData); 165 | cache.list.set(object.uuid, object); 166 | 167 | if (element.parentNode !== domElement) { 168 | 169 | domElement.appendChild(element); 170 | 171 | } 172 | 173 | object.onAfterRender(_this, scene, camera); 174 | 175 | } 176 | } 177 | 178 | for (let i = 0, l = object.children.length; i < l; i++) { 179 | 180 | renderObject(object.children[i], scene, camera); 181 | 182 | } 183 | 184 | 185 | } 186 | 187 | function getDistanceToSquared(object1, object2) { 188 | 189 | _a.setFromMatrixPosition(object1.matrixWorld); 190 | 191 | _b.setFromMatrixPosition(object2.matrixWorld); 192 | 193 | return _a.distanceToSquared(_b); 194 | 195 | } 196 | 197 | function filterAndFlatten(scene) { 198 | 199 | const result = []; 200 | scene.traverse(function (object) { 201 | 202 | if (object.isCSS2DObject) result.push(object); 203 | 204 | }); 205 | return result; 206 | 207 | } 208 | 209 | function zOrder(scene) { 210 | 211 | const sorted = filterAndFlatten(scene).sort(function (a, b) { 212 | //[jscastro] check the objects already exist in the cache 213 | let cacheA = cache.objects.get({ key: a.uuid }); 214 | let cacheB = cache.objects.get({ key: b.uuid }); 215 | 216 | if (cacheA && cacheB) { 217 | const distanceA = cacheA.distanceToCameraSquared; 218 | const distanceB = cacheB.distanceToCameraSquared; 219 | return distanceA - distanceB; 220 | } 221 | 222 | }); 223 | 224 | const zMax = sorted.length; 225 | 226 | for (let i = 0, l = sorted.length; i < l; i++) { 227 | 228 | sorted[i].element.style.zIndex = zMax - i; 229 | 230 | } 231 | 232 | } 233 | 234 | } 235 | 236 | } 237 | 238 | THREE.CSS2DObject = CSS2DObject; 239 | THREE.CSS2DRenderer = CSS2DRenderer; 240 | 241 | })(); 242 | 243 | module.exports = exports = { CSS2DRenderer: THREE.CSS2DRenderer, CSS2DObject: THREE.CSS2DObject }; 244 | 245 | -------------------------------------------------------------------------------- /src/objects/effects/BuildingShadows.js: -------------------------------------------------------------------------------- 1 | const SunCalc = require('../../utils/suncalc.js'); 2 | 3 | class BuildingShadows { 4 | constructor(options, threebox) { 5 | this.id = options.layerId; 6 | this.type = 'custom'; 7 | this.renderingMode = '3d'; 8 | this.opacity = 0.5; 9 | this.buildingsLayerId = options.buildingsLayerId; 10 | this.minAltitude = options.minAltitude || 0.10; 11 | this.tb = threebox; 12 | } 13 | onAdd(map, gl) { 14 | this.map = map; 15 | // find layer source 16 | const sourceName = this.map.getLayer(this.buildingsLayerId).source; 17 | this.source = (this.map.style.sourceCaches || this.map.style._otherSourceCaches)[sourceName]; 18 | if (!this.source) { 19 | console.warn(`Can't find layer ${this.buildingsLayerId}'s source.`); 20 | } 21 | 22 | // vertex shader of fill-extrusion layer is different in mapbox v1 and v2. 23 | // https://github.com/mapbox/mapbox-gl-js/commit/cef95aa0241e748b396236f1269fbb8270f31565 24 | const vertexSource = this._getVertexSource(); 25 | const fragmentSource = ` 26 | void main() { 27 | gl_FragColor = vec4(0.0, 0.0, 0.0, 0.7); 28 | } 29 | `; 30 | const vertexShader = gl.createShader(gl.VERTEX_SHADER); 31 | gl.shaderSource(vertexShader, vertexSource); 32 | gl.compileShader(vertexShader); 33 | const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); 34 | gl.shaderSource(fragmentShader, fragmentSource); 35 | gl.compileShader(fragmentShader); 36 | this.program = gl.createProgram(); 37 | gl.attachShader(this.program, vertexShader); 38 | gl.attachShader(this.program, fragmentShader); 39 | gl.linkProgram(this.program); 40 | gl.validateProgram(this.program); 41 | this.uMatrix = gl.getUniformLocation(this.program, "u_matrix"); 42 | this.uHeightFactor = gl.getUniformLocation(this.program, "u_height_factor"); 43 | this.uAltitude = gl.getUniformLocation(this.program, "u_altitude"); 44 | this.uAzimuth = gl.getUniformLocation(this.program, "u_azimuth"); 45 | 46 | if (this.tb.mapboxVersion >= 2.0) { 47 | this.aPosNormal = gl.getAttribLocation(this.program, "a_pos_normal_ed"); 48 | } else { 49 | this.aPos = gl.getAttribLocation(this.program, "a_pos"); 50 | this.aNormal = gl.getAttribLocation(this.program, "a_normal_ed"); 51 | } 52 | 53 | this.aBase = gl.getAttribLocation(this.program, "a_base"); 54 | this.aHeight = gl.getAttribLocation(this.program, "a_height"); 55 | } 56 | render(gl, matrix) { 57 | if (!this.source) return; 58 | 59 | gl.useProgram(this.program); 60 | const coords = this.source.getVisibleCoordinates().reverse(); 61 | const buildingsLayer = this.map.getLayer(this.buildingsLayerId); 62 | const context = this.map.painter.context; 63 | const { lng, lat } = this.map.getCenter(); 64 | const pos = this.tb.getSunPosition(this.tb.lightDateTime, [lng, lat]); 65 | gl.uniform1f(this.uAltitude, (pos.altitude > this.minAltitude ? pos.altitude : 0)); 66 | gl.uniform1f(this.uAzimuth, pos.azimuth + 3 * Math.PI / 2); 67 | //this.opacity = Math.sin(Math.max(pos.altitude, 0)) * 0.6; 68 | gl.enable(gl.BLEND); 69 | //gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.DST_ALPHA, gl.SRC_ALPHA); 70 | gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA) 71 | var ext = gl.getExtension('EXT_blend_minmax'); 72 | //gl.blendEquationSeparate(gl.FUNC_SUBTRACT, ext.MIN_EXT); 73 | //gl.blendEquation(gl.FUNC_ADD); 74 | gl.disable(gl.DEPTH_TEST); 75 | for (const coord of coords) { 76 | const tile = this.source.getTile(coord); 77 | const bucket = tile.getBucket(buildingsLayer); 78 | if (!bucket) continue; 79 | const [heightBuffer, baseBuffer] = bucket.programConfigurations.programConfigurations[this.buildingsLayerId]._buffers; 80 | gl.uniformMatrix4fv(this.uMatrix, false, (coord.posMatrix || coord.projMatrix)); 81 | gl.uniform1f(this.uHeightFactor, Math.pow(2, coord.overscaledZ) / tile.tileSize / 8); 82 | for (const segment of bucket.segments.get()) { 83 | const numPrevAttrib = context.currentNumAttributes || 0; 84 | const numNextAttrib = 2; 85 | for (let i = numNextAttrib; i < numPrevAttrib; i++) gl.disableVertexAttribArray(i); 86 | const vertexOffset = segment.vertexOffset || 0; 87 | gl.enableVertexAttribArray(this.aNormal); 88 | gl.enableVertexAttribArray(this.aHeight); 89 | gl.enableVertexAttribArray(this.aBase); 90 | bucket.layoutVertexBuffer.bind(); 91 | if (this.tb.mapboxVersion >= 2.0) { 92 | gl.enableVertexAttribArray(this.aPosNormal); 93 | gl.vertexAttribPointer(this.aPosNormal, 4, gl.SHORT, false, 8, 8 * vertexOffset); 94 | } else { 95 | gl.enableVertexAttribArray(this.aPos); 96 | gl.vertexAttribPointer(this.aPos, 2, gl.SHORT, false, 12, 12 * vertexOffset); 97 | gl.vertexAttribPointer(this.aNormal, 4, gl.SHORT, false, 12, 4 + 12 * vertexOffset); 98 | } 99 | 100 | heightBuffer.bind(); 101 | gl.vertexAttribPointer(this.aHeight, 1, gl.FLOAT, false, 4, 4 * vertexOffset); 102 | baseBuffer.bind(); 103 | gl.vertexAttribPointer(this.aBase, 1, gl.FLOAT, false, 4, 4 * vertexOffset); 104 | bucket.indexBuffer.bind(); 105 | context.currentNumAttributes = numNextAttrib; 106 | gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); 107 | } 108 | } 109 | } 110 | 111 | _getVertexSource() { 112 | if (this.tb.mapboxVersion >= 2.0) { 113 | return ` 114 | uniform mat4 u_matrix; 115 | uniform float u_height_factor; 116 | uniform float u_altitude; 117 | uniform float u_azimuth; 118 | attribute vec4 a_pos_normal_ed; 119 | attribute lowp vec2 a_base; 120 | attribute lowp vec2 a_height; 121 | void main() { 122 | float base = max(0.0, a_base.x); 123 | float height = max(0.0, a_height.x); 124 | 125 | vec3 pos_nx = floor(a_pos_normal_ed.xyz * 0.5); 126 | mediump vec3 top_up_ny = a_pos_normal_ed.xyz - 2.0 * pos_nx; 127 | float t = top_up_ny.x; 128 | vec4 pos = vec4(pos_nx.xy, t > 0.0 ? height : base, 1); 129 | 130 | float len = pos.z * u_height_factor / tan(u_altitude); 131 | pos.x += cos(u_azimuth) * len; 132 | pos.y += sin(u_azimuth) * len; 133 | pos.z = 0.0; 134 | gl_Position = u_matrix * pos; 135 | } 136 | `; 137 | } else { 138 | return ` 139 | uniform mat4 u_matrix; 140 | uniform float u_height_factor; 141 | uniform float u_altitude; 142 | uniform float u_azimuth; 143 | attribute vec2 a_pos; 144 | attribute vec4 a_normal_ed; 145 | attribute lowp vec2 a_base; 146 | attribute lowp vec2 a_height; 147 | void main() { 148 | float base = max(0.0, a_base.x); 149 | float height = max(0.0, a_height.x); 150 | float t = mod(a_normal_ed.x, 2.0); 151 | vec4 pos = vec4(a_pos, t > 0.0 ? height : base, 1); 152 | float len = pos.z * u_height_factor / tan(u_altitude); 153 | pos.x += cos(u_azimuth) * len; 154 | pos.y += sin(u_azimuth) * len; 155 | pos.z = 0.0; 156 | gl_Position = u_matrix * pos; 157 | } 158 | `; 159 | } 160 | } 161 | } 162 | 163 | 164 | module.exports = exports = BuildingShadows; -------------------------------------------------------------------------------- /examples/16-multilayer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threbox multilayer sample 4 | 5 | 6 | 7 | 8 | 9 | 63 | 64 | 65 | 66 |
67 | 208 | -------------------------------------------------------------------------------- /tests/unit/utilities.test.js: -------------------------------------------------------------------------------- 1 | function utilitiesTest(instance){ 2 | 3 | test('PROJECTION project / unproject', function(t) { 4 | 5 | var coord, projected, expected; 6 | 7 | coord = [0,0,0]; 8 | var projected = instance.projectToWorld(coord); 9 | var unprojected = instance.unprojectFromWorld(projected); 10 | var expected = new THREE.Vector3(0,0,0); 11 | vector3Equals(t, projected, expected); 12 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 13 | 14 | coord = [30,30,0]; 15 | var projected = instance.projectToWorld(coord); 16 | var unprojected = instance.unprojectFromWorld(projected); 17 | var expected = new THREE.Vector3(-85333.33333333333, -89522.98305691125, 0); 18 | vector3Equals(t, projected, expected); 19 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 20 | 21 | coord = [30,-30,0]; 22 | var projected = instance.projectToWorld(coord); 23 | var unprojected = instance.unprojectFromWorld(projected); 24 | var expected = new THREE.Vector3(-85333.33333333333, -89522.9830569113, 0); 25 | vector3Equals(t, projected, expected); 26 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 27 | 28 | coord = [-30,30,0]; 29 | var projected = instance.projectToWorld(coord); 30 | var unprojected = instance.unprojectFromWorld(projected); 31 | var expected = new THREE.Vector3(-85333.33333333333, -89522.9830569113, 0); 32 | vector3Equals(t, projected, expected); 33 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 34 | 35 | coord = [-30,-30,0]; 36 | var projected = instance.projectToWorld(coord); 37 | var unprojected = instance.unprojectFromWorld(projected); 38 | var expected = new THREE.Vector3(-85333.33333333333, 89522.9830569113, 0); 39 | vector3Equals(t, projected, expected); 40 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 41 | 42 | t.end(); 43 | }); 44 | 45 | test('PROJECTION project / unproject extended lat/lng range', function(t) { 46 | var coord, projected, expected; 47 | 48 | coord = [180,0,0]; 49 | var projected = instance.projectToWorld(coord); 50 | var unprojected = instance.unprojectFromWorld(projected); 51 | var expected = new THREE.Vector3(-511999.99999999994,1.8093822187881337e-11,0); 52 | vector3Equals(t, projected, expected); 53 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 54 | 55 | coord = [-180,0,0]; 56 | var projected = instance.projectToWorld(coord); 57 | var unprojected = instance.unprojectFromWorld(projected); 58 | var expected = new THREE.Vector3(511999.99999999994,1.8093822187881337e-11,0); 59 | vector3Equals(t, projected, expected); 60 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 61 | 62 | coord = [0,90,0]; 63 | var projected = instance.projectToWorld(coord); 64 | var unprojected = instance.unprojectFromWorld(projected); 65 | var expected = new THREE.Vector3(0,-3042.073317352722,0); 66 | vector3Equals(t, projected, expected); 67 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 68 | 69 | 70 | coord = [0, 85.051129,0]; 71 | var projected = instance.projectToWorld(coord); 72 | var unprojected = instance.unprojectFromWorld(projected); 73 | var expected = new THREE.Vector3(0, -512000.00726036413, 0); 74 | vector3Equals(t, projected, expected); 75 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 76 | 77 | coord = [0, -85.051129,0]; 78 | var projected = instance.projectToWorld(coord); 79 | var unprojected = instance.unprojectFromWorld(projected); 80 | var expected = new THREE.Vector3(0, -512000.00726036413, 0); 81 | vector3Equals(t, projected, expected); 82 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 83 | 84 | 85 | coord = [300,0,0]; 86 | var projected = instance.projectToWorld(coord); 87 | var unprojected = instance.unprojectFromWorld(projected); 88 | var expected = new THREE.Vector3(-853333.3333333333,1.8093822187881337e-11,0); 89 | vector3Equals(t, projected, expected); 90 | vector3Equals(t, new THREE.Vector3(unprojected), new THREE.Vector3(expected)); 91 | 92 | t.end(); 93 | }); 94 | 95 | 96 | test('PROJECTION with altitude', function(t) { 97 | var coord, projected, expected; 98 | 99 | coord = [0,0,10000]; 100 | var projected = instance.projectToWorld(coord); 101 | var expected = new THREE.Vector3(0,0,255.52089831565812); 102 | vector3Equals(t, projected, expected); 103 | 104 | 105 | coord = [0,0,-10000]; 106 | var projected = instance.projectToWorld(coord); 107 | var expected = new THREE.Vector3(0,0,-255.52089831565812); 108 | vector3Equals(t, projected, expected); 109 | 110 | t.end(); 111 | }); 112 | 113 | test('PROJECTION projectedUnitsPerMeter', function(t) { 114 | 115 | var pupm1 = instance.projectedUnitsPerMeter(10); 116 | var pupm2 = instance.projectedUnitsPerMeter(-35); 117 | 118 | t.equals(pupm1, 0.025946272004267072); 119 | t.equals(pupm2, 0.03119334195612554); 120 | 121 | t.end(); 122 | }); 123 | 124 | 125 | // test('PROJECTION project / unproject invalid input', function(t) { 126 | // // TODO: Check for null/undefined/NaN values 127 | // t.end(); 128 | // }); 129 | 130 | 131 | test('NORMALIZEVERTICES', function(t){ 132 | 133 | var input = [ 134 | new THREE.Vector3(100, 101, 102), 135 | new THREE.Vector3(103, 104, 105) 136 | ]; 137 | 138 | var normalized = instance.utils.normalizeVertices(input); 139 | 140 | t.deepEqual( 141 | normalized.position, 142 | { 143 | x: 101.5, 144 | y: 102.5, 145 | z: 103.5 146 | } 147 | ); 148 | 149 | t.deepEqual( 150 | normalized.vertices, 151 | [ 152 | {x: -1.5, y: -1.5, z: -1.5}, 153 | {x: 1.5, y: 1.5, z: 1.5} 154 | ] 155 | ); 156 | 157 | t.end(); 158 | }) 159 | 160 | var defaults = { 161 | foo: 'bar', 162 | biz: false 163 | } 164 | 165 | test('VALIDATOR empty input', function(t) { 166 | var output = instance.utils._validate({}, defaults); 167 | t.deepEqual(output, defaults); 168 | t.end(); 169 | }); 170 | 171 | test('VALIDATOR does not overwrite unknown props', function(t) { 172 | var output = instance.utils._validate({a:true}, defaults); 173 | 174 | t.deepEqual(output, { 175 | foo: 'bar', 176 | biz: false, 177 | a: true 178 | }); 179 | 180 | t.end(); 181 | }); 182 | 183 | test('VALIDATOR missing required params throw error', function(t) { 184 | var output = instance.utils._validate({}, {b: null}); 185 | t.error(output, 'proper error'); 186 | t.end(); 187 | }); 188 | } 189 | -------------------------------------------------------------------------------- /examples/09-raycaster.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threebox raycaster of Objects3D, 3D models and Fill-extrusions 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 |
24 | 25 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /examples/19-fixedzoom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threebox fixed zoom 4 | 5 | 6 | 7 | 8 | 9 | 21 | 22 | 23 | 24 |
25 | 26 | 365 | 366 | -------------------------------------------------------------------------------- /examples/15-performance.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Threebox performance test 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 48 | 49 | 50 | 51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 | 59 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /examples/20-game.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threebox WASD driving game 4 | 5 | 6 | 7 | 8 | 9 | 45 | 46 | 47 | 48 |
49 |
50 |
51 | Press "W", "A", "S", "D" keys to drive the truck 52 |
53 |
54 |
55 | 56 | 339 | 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `Threebox` 2 | 3 | ![npm](https://img.shields.io/npm/dt/threebox-plugin?style=social) 4 | ![npm](https://img.shields.io/npm/dw/threebox-plugin?style=flat-square) 5 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/jscastro76/threebox?style=flat-square)](https://github.com/jscastro76/threebox/releases/) 6 | [![NPM version](http://img.shields.io/npm/v/threebox-plugin.svg?style=flat-square)](https://www.npmjs.org/package/threebox-plugin) 7 | [![NPM license](http://img.shields.io/npm/l/threebox-plugin.svg?style=flat-square)](https://www.npmjs.org/package/threebox-plugin) 8 | 9 | A **[*Three.js*](https://threejs.org/)** plugin for **[*Mapbox GL JS*](https://docs.mapbox.com/mapbox-gl-js/examples/)** and **[*Azure Maps*](https://azure.microsoft.com/en-us/services/azure-maps/)** using the [`CustomLayerInterface`](https://docs.mapbox.com/mapbox-gl-js/api/properties/#customlayerinterface) feature. Provides convenient methods to manage objects in lnglat coordinates, and to synchronize the map and scene cameras. 10 | threebox 11 | 12 |
13 | 14 | - - - 15 | ## Latest release 16 | 17 | ![GitHub Release Date](https://img.shields.io/github/release-date/jscastro76/threebox?style=flat-square) 18 | Latest **code release** is [![GitHub release (latest by date)](https://img.shields.io/github/v/release/jscastro76/threebox?style=flat-square)](https://github.com/jscastro76/threebox/releases/), please review the [**Change log**](https://github.com/jscastro76/threebox/blob/master/CHANGELOG.md) for more details. 19 | 20 | Threebox is also available as an **nmp package** [![NPM version](http://img.shields.io/npm/v/threebox-plugin.svg?style=flat-square)](https://www.npmjs.org/package/threebox-plugin) 21 | 22 | ```js 23 | npm i threebox-plugin 24 | ``` 25 |
26 | 27 | - - - 28 | 29 | ## ONLY in this Threebox fork 30 | 31 | |Models built-in & custom animations |Mouse over/out, Selected, Drag&Drop, Drag&Rotate, Wireframe 32 | |---------|----------------------- 33 | |threebox|threebox 34 | 35 | |Tooltips using altitude|Optimization of camera perspective and depth 36 | |----------|------- 37 | |threebox|threebox 38 | 39 | |Runtime style change|Optimized performance through cache 40 | |----------|------- 41 | |threebox|threebox 42 | 43 | |Customizable FOV|Geojson and Points Extrusions 44 | |---------|------- 45 | |threebox|threebox 46 | 47 | |Sunlight illumination for a given datetime and lnglat|Models built-in shadows and sky layer synced with Sunlight 48 | |---------|------- 49 | |threebox|threebox 50 | 51 |
52 | 53 | Only in this fork, there is a list of new features implemented on top of the amazing work from [@peterqliu](https://github.com/peterqliu/threebox/): 54 | - Updated to [**Three.js r132**](https://github.com/mrdoob/three.js/releases/tag/r132). 55 | - Updated to **Mapbox-gl-js v2.2.0**. 56 | - Updated to **Azure Maps v2.0.31**. 57 | - [+20 examples](https://github.com/jscastro76/threebox/tree/master/examples) with all the new features. 58 | - Support for multiple 3D format objects (FBX, GLTF/GLB, Collada, OBJ/MTL). 59 | - Support for 3D extruded shapes from [GeoJson](https://geojson.org/) features or points array. 60 | - Support for CSS2D labels and rich HTML controls through a new LabelManager. 61 | - Support for CSS2D tooltips/title browser-like and mapbox-like. 62 | - Support for built-in Raycaster in Object3D and fill-extrusions together. 63 | - Support for built-in MouseOver/Mouseout, Selected, Drag&Drop, Drag&Rotate, Wireframe in loadedObjects including events. 64 | - Support for wireframing on any Object3D, removing them from the Raycaster. 65 | - Support for [GeoJson](https://geojson.org/) standard features format import and export in different layers. 66 | - Support for Object3D embedded animations, and custom animations on AnimationManager (i.e. embedded animation + translate + rotate). 67 | - Support for multi-layer and multi-floor design of spaces. 68 | - Support for built-in shadows and real Sun light positioning for a given datetime and lnglat coords. 69 | - Support for built-in Mapbox v2 Sky and Terrain layer synced with real Sun light. 70 | - Support for Non-AABB Non Axes Aligned Bounding Box and real model size, including floor projection. 71 | - Support for Object3D auto-centering and 9 default anchor positions customizable through adjustments. 72 | - Support for `setLayerZoomRange` and `setLayoutProperty` on Custom Layers (not available in Mapbox). 73 | - Support for `removeLayer` considering Object3D. 74 | - Support for style change through `setStyle` and keeping Object3D. 75 | - Support for partial and full dispose of Mapbox, Three and Threebox resources and memory. 76 | - Support for Orthographic view, customizable Perspective FOV and fixed-size Object3D. 77 | - Optimization of Camera perspective to have Raycast with pixel-precision level and depth sync between Mapbox and Threebox objects. 78 | - Optimization for loading thousands of objects through cache. 79 | - Available as [npm package](https://www.npmjs.com/package/threebox-plugin) 80 | - Check out [change log](https://github.com/jscastro76/threebox/blob/master/CHANGELOG.md) for more detail. 81 | 82 |
83 | 84 | - - - 85 | 86 | 87 | ## Documentation 88 | threebox 89 | 90 | All the [**Threebox Documentation**](/docs/Threebox.md) has been completely updated, including all the methods, properties and events implemented in Threebox and objects, but still *'work in progress'* adding better documented examples and images to illustrate Threebox capabilities. 91 | - [**Using Threebox**](/docs/Threebox.md#using-threebox) 92 | - [**Loading a 3D Model**](/docs/Threebox.md#loading-a-3d-model) 93 | - [**Threebox methods**](/docs/Threebox.md#threebox-methods) 94 | - [**Object methods**](/docs/Threebox.md#object-methods) 95 | - [**Examples**](/examples/README.md) 96 | 97 |
98 | 99 | - - - 100 | 101 | ## Compatibility/Dependencies 102 | 103 | - [**Three.js 132**](https://github.com/mrdoob/three.js/releases/tag/r132). (already bundled into the Threebox build). If desired, other versions can be swapped in and rebuilt [here](https://github.com/jscastro76/threebox/blob/master/src/three.js), though compatibility is not guaranteed. 104 | - **Mapbox-gl-js v1.11.1. or v.2.0.1**. **Warning**: Despite v1.11.1 still supported, if used, some features from mapbox v.2.0.1 won't be obviously available such as sky layers. 105 | - **Azure Maps v2.0.31.** 106 | 107 |
108 | 109 | - - - 110 | 111 | ## Getting started 112 | 113 | You can use threebox in three different ways. 114 | 115 | #### NPM install 116 | Add threebox to your project via **npm package** [![NPM version](http://img.shields.io/npm/v/threebox-plugin.svg?style=flat-square)](https://www.npmjs.org/package/threebox-plugin) : 117 | ```js 118 | npm install threebox-plugin 119 | ``` 120 | 121 | Then you will need to import Threebox object in your code. Depending your javascript framework this might be different. 122 | ```js 123 | import { Threebox } from 'threebox-plugin'; 124 | ``` 125 | Depending the framework, wrapper or bundler you ar using, try with this: 126 | ```js 127 | import { Threebox } from 'threebox-plugin/dist/threebox'; 128 | ``` 129 | 130 |
131 | 132 | #### Use the bundle locally 133 | Download the bundle from [`dist/threebox.js`](dist/threebox.js) or [`dist/threebox.min.js`](dist/threebox.min.js) and include it in a ` 137 | 138 | ``` 139 |
140 | 141 | #### Public CDNs 142 | Threebox can be also used from different public CDNs: 143 | 144 | ##### jsdelivr 145 | This CDN has the particularity that always requires the version of the package to download individual files. 146 | ```html 147 | 148 | 149 | ``` 150 | 151 |
152 | 153 | ##### unpkg 154 | Despite this CDN admits version, if omitted, it will download always the last one published. 155 | 156 | ```html 157 | 158 | 159 | ``` 160 | 161 | For an specific version (i.e. v2.2.1) use the followin: 162 | ```html 163 | 164 | 165 | ``` 166 | 167 |
168 | 169 | #### Test the samples 170 | Several introductory examples are [here](https://github.com/jscastro76/threebox/tree/master/examples). 171 | To run them, create a `config.js` file with your Mapbox-gl-js access token, alongside and in the format of [the template](https://github.com/jscastro76/threebox/blob/master/examples/config_template.js). 172 | 173 |
174 | 175 | - - - 176 | 177 | ## Contributing 178 | - Clone the [Github repo](https://github.com/jscastro76/threebox/). 179 | - Build the library with `npm run build` to get the minimized version, or `npm run dev` to get the development version and rebuild continuously as you develop. 180 | - Both commands will output a bundle in [`dist/`](dist/) folder. 181 | 182 | #### Unit tests 183 | Tests live [here](/tests). 184 | - Build first the test bundle with `npm run test`, this will create [`tests\threebox-tests-bundle.js`](tests/threebox-tests-bundle.js) 185 | - Then in your preferred browser navigate to [`threebox-tests.html`](https://github.com/jscastro76/threebox/blob/master/tests/threebox-tests.html) and check the console for test results. 186 | 187 | #### How to build the project in Visual Studio 188 | Sample to get a full build from scratch for Visual Studio: 189 | - Install [Node.js](https://nodejs.org/en/) 190 | - Clone the repo and open a new Project using main.js 191 | - Install / Update the packages browserify, tape, ncp, uglyfy, watchify. 192 | - Right click on the project at the Solution Explorer > Open Node.js Interactive Window: 193 | - execute `.npm [ProjectName] init -y` 194 | - execute `.npm [ProjectName] install` 195 | - execute `.npm [ProjectName] i` 196 | - execute `.npm [ProjectName] run dev` or `.npm run build 197 | ` 198 | 199 | 200 | -------------------------------------------------------------------------------- /examples/11-animation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Threebox animated soldier 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 90 | 91 | 92 |
93 |
94 |
95 | 96 | 97 |
98 |
99 |
100 |
101 | Add or Select an object 102 |
103 |
104 |
105 |
Select the soldier, drag, rotate, wireframe and play animation
106 | 107 | 363 | --------------------------------------------------------------------------------