UNPKG

@enable3d/three-graphics

Version:

3D library wrapping three.js and ammo.js

193 lines 7.07 kB
/** * @author Yannick Deubel (https://github.com/yandeu) * @copyright Copyright (c) 2021 Yannick Deubel; Project Url: https://github.com/enable3d/enable3d * @license {@link https://github.com/enable3d/enable3d/blob/master/LICENSE|LGPL-3.0} */ import { LinearFilter, Raycaster, Texture, Vector2 } from 'three'; import { Tap } from '@yandeu/tap'; // https://stackoverflow.com/a/7838871 export const roundRect = (ctx, x, y, w, h, r) => { if (w < 2 * r) r = w / 2; if (h < 2 * r) r = h / 2; ctx.beginPath(); ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r); ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath(); }; export const fontHeightCache = new Map(); export const calcHeight = (text, fontSize, fontFamily, lineHeight = 1) => { const key = fontSize + fontFamily; let height = fontHeightCache.get(key); if (!height) { // https://stackoverflow.com/a/10500938 const span = document.createElement('p'); span.style.fontFamily = fontFamily; span.style.fontSize = `${fontSize}px`; span.style.whiteSpace = 'nowrap'; span.style.lineHeight = lineHeight.toString(); span.textContent = text; document.body.appendChild(span); height = Math.ceil(span.offsetHeight); document.body.removeChild(span); // add to cache fontHeightCache.set(key, height); } return height; }; export const calcWidth = (ctx, lines) => { // return the longest line return Math.max(...lines.map(line => Math.ceil(ctx.measureText(line).width))); }; export const createNewTexture = (image) => { const texture = new Texture(image); texture.minFilter = LinearFilter; texture.generateMipmaps = false; // texture.magFilter = NearestFilter // texture.minFilter = NearestFilter // texture.magFilter = NearestFilter // texture.minFilter = LinearMipMapLinearFilter texture.needsUpdate = true; return texture; }; // create the canvas for the texture export const canvas = document.createElement('canvas'); let parent; let orbitCtl; let tap; let down = false; const objects = []; const raycaster = new Raycaster(); const mouse = new Vector2(); const pos = new Vector2(); const size = new Vector2(); const messages = []; const warn = (msg) => { if (messages.includes(msg)) return; console.warn(msg); messages.push(msg); }; export const setSize = (width, height) => { size.x = width; size.y = height; }; export const setParent = (canvas) => { parent = canvas; }; export const getParent = () => parent; export const setOrbitControls = (orbitControls) => { if (orbitControls) orbitCtl = orbitControls; }; const addEventListeners = () => { // get the canvas element for the input events const canvas = getParent(); if (!canvas) { warn('Please call "FLAT.initEvents()" first.'); return; } tap = new Tap(canvas); }; export const destroy = () => { clearObjects(); if (tap) tap.destroy(); }; export const addObject = (object) => { if (objects.length === 0) addEventListeners(); objects.push(object); }; export const clearObjects = () => { while (objects.length > 0) { objects.pop(); } }; // TODO(yandeu) Don't call this in a update loop! Call it on an input event!!! Or not? I don't know :/ export const updateEvents = async (camera) => { // console.log(tap.currentPosition) if (!tap) return; const { currentPosition: { x, y }, isDown } = tap; const hasMouseMoved = mouse.x !== x || mouse.y !== y; const hasClicked = down !== isDown; if (!hasMouseMoved && !hasClicked) return; down = isDown; mouse.x = x; mouse.y = y; if (size.x === 0 || typeof size.x === 'undefined') { warn('[FLAT] Please call FLAT.setSize() first!'); return; } // calculate mouse position in normalized device coordinates // (-1 to +1) for both components pos.x = (x / size.x) * 2 - 1; pos.y = -(y / size.y) * 2 + 1; // update the picking ray with the camera and mouse position raycaster.setFromCamera(pos, camera); let _objects = [...objects]; // calculate objects intersecting the picking ray const intersects = raycaster.intersectObjects(_objects); // console.log('i', intersects) if (intersects.length === 0) document.body.style.cursor = 'default'; else document.body.style.cursor = 'pointer'; if (orbitCtl && orbitCtl.enabled && intersects.length >= 0) orbitCtl.enabled = false; if (orbitCtl && !orbitCtl.enabled && intersects.length === 0) orbitCtl.enabled = true; for (let i = 0; i < intersects.length; i++) { const object = intersects[i].object; let isTransparent = false; if (object.pixelPerfect) { // https://github.com/mrdoob/three.js/issues/758 const getImageData = (image) => { // const canvas = document.createElement('canvas') canvas.width = image.width; canvas.height = image.height; const context = canvas.getContext('2d'); context.drawImage(image, 0, 0); return context.getImageData(0, 0, image.width, image.height); }; const getPixel = (imagedata, x, y) => { const position = (x + imagedata.width * y) * 4; const data = imagedata.data; return { r: data[position], g: data[position + 1], b: data[position + 2], a: data[position + 3] }; }; const uv = intersects[0].uv; const { x, y } = object.texture.transformUv(uv); const bitmap = await createImageBitmap(object.texture.image); const imageData = getImageData(bitmap); const { r, g, b, a } = getPixel(imageData, Math.round(x * imageData.width), Math.round(y * imageData.height)); isTransparent = r + g + b + a === 0; } // get the colors of the pixel // var gl = renderer.getContext() // var pixels = new Uint8Array(1 * 1 * 4) // gl.readPixels(client.x, window.innerHeight - client.y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels) // console.log(pixels) // Uint8Array // we skip to the next object in intersects[] if this pixel is transparent if (object.pixelPerfect && isTransparent) continue; // set event object.event = tap.isDown ? 'down' : 'over'; // Removing Items in Arrays const removeIndex = _objects.findIndex(o => o.uuid === object.uuid); _objects = [..._objects.slice(0, removeIndex), ..._objects.slice(removeIndex + 1)]; // we do not want to send events to objects that lay lower break; } // send out event remaining objects _objects.forEach(o => { o.event = 'out'; }); return intersects; }; //# sourceMappingURL=_misc.js.map