download
raw
28.9 kB
import {
Color,
DoubleSide,
Matrix4,
Mesh,
MeshBasicMaterial,
PlaneGeometry,
Vector3,
Vector2,
} from 'three'
import { GlyphsGeometry } from './GlyphsGeometry.js'
import { createTextDerivedMaterial } from './TextDerivedMaterial.js'
import { getTextRenderInfo } from './TextBuilder.js'
const defaultMaterial = /*#__PURE__*/ new MeshBasicMaterial({
color: 0xffffff,
side: DoubleSide,
transparent: true
})
const defaultStrokeColor = 0x808080
const tempMat4 = /*#__PURE__*/ new Matrix4()
const tempVec3a = /*#__PURE__*/ new Vector3()
const tempVec3b = /*#__PURE__*/ new Vector3()
const tempArray = []
const origin = /*#__PURE__*/ new Vector3()
const defaultOrient = '+x+y'
function first(o) {
return Array.isArray(o) ? o[0] : o
}
let getFlatRaycastMesh = () => {
const mesh = new Mesh(
new PlaneGeometry(1, 1),
defaultMaterial
)
getFlatRaycastMesh = () => mesh
return mesh
}
let getCurvedRaycastMesh = () => {
const mesh = new Mesh(
new PlaneGeometry(1, 1, 32, 1),
defaultMaterial
)
getCurvedRaycastMesh = () => mesh
return mesh
}
const syncStartEvent = { type: 'syncstart' }
const syncCompleteEvent = { type: 'synccomplete' }
const SYNCABLE_PROPS = [
'font',
'fontSize',
'fontStyle',
'fontWeight',
'lang',
'letterSpacing',
'lineHeight',
'maxWidth',
'overflowWrap',
'text',
'direction',
'textAlign',
'textIndent',
'whiteSpace',
'anchorX',
'anchorY',
'colorRanges',
'sdfGlyphSize'
]
const COPYABLE_PROPS = SYNCABLE_PROPS.concat(
'material',
'color',
'depthOffset',
'clipRect',
'curveRadius',
'orientation',
'glyphGeometryDetail'
)
/**
* @class Text
*
* A ThreeJS Mesh that renders a string of text on a plane in 3D space using signed distance
* fields (SDF).
*/
class Text extends Mesh {
constructor() {
const geometry = new GlyphsGeometry()
super(geometry, null)
// === Text layout properties: === //
/**
* @member {string} text
* The string of text to be rendered.
*/
this.text = ''
/**
* @member {number|string} anchorX
* Defines the horizontal position in the text block that should line up with the local origin.
* Can be specified as a numeric x position in local units, a string percentage of the total
* text block width e.g. `'25%'`, or one of the following keyword strings: 'left', 'center',
* or 'right'.
*/
this.anchorX = 0
/**
* @member {number|string} anchorY
* Defines the vertical position in the text block that should line up with the local origin.
* Can be specified as a numeric y position in local units (note: down is negative y), a string
* percentage of the total text block height e.g. `'25%'`, or one of the following keyword strings:
* 'top', 'top-baseline', 'top-cap', 'top-ex', 'middle', 'bottom-baseline', or 'bottom'.
*/
this.anchorY = 0
/**
* @member {number} curveRadius
* Defines a cylindrical radius along which the text's plane will be curved. Positive numbers put
* the cylinder's centerline (oriented vertically) that distance in front of the text, for a concave
* curvature, while negative numbers put it behind the text for a convex curvature. The centerline
* will be aligned with the text's local origin; you can use `anchorX` to offset it.
*
* Since each glyph is by default rendered with a simple quad, each glyph remains a flat plane
* internally. You can use `glyphGeometryDetail` to add more vertices for curvature inside glyphs.
*/
this.curveRadius = 0
/**
* @member {string} direction
* Sets the base direction for the text. The default value of "auto" will choose a direction based
* on the text's content according to the bidi spec. A value of "ltr" or "rtl" will force the direction.
*/
this.direction = 'auto'
/**
* @member {string|null} font
* URL of a custom font to be used. Font files can be in .ttf, .otf, or .woff (not .woff2) formats.
* Defaults to Noto Sans.
*/
this.font = null //will use default from TextBuilder
this.unicodeFontsURL = null //defaults to CDN
/**
* @member {number} fontSize
* The size at which to render the font in local units; corresponds to the em-box height
* of the chosen `font`.
*/
this.fontSize = 0.1
/**
* @member {number|'normal'|'bold'}
* The weight of the font. Currently only used for fallback Noto fonts.
*/
this.fontWeight = 'normal'
/**
* @member {'normal'|'italic'}
* The style of the font. Currently only used for fallback Noto fonts.
*/
this.fontStyle = 'normal'
/**
* @member {string|null} lang
* The language code of this text; can be used for explicitly selecting certain CJK fonts.
*/
this.lang = null;
/**
* @member {number} letterSpacing
* Sets a uniform adjustment to spacing between letters after kerning is applied. Positive
* numbers increase spacing and negative numbers decrease it.
*/
this.letterSpacing = 0
/**
* @member {number|string} lineHeight
* Sets the height of each line of text, as a multiple of the `fontSize`. Defaults to 'normal'
* which chooses a reasonable height based on the chosen font's ascender/descender metrics.
*/
this.lineHeight = 'normal'
/**
* @member {number} maxWidth
* The maximum width of the text block, above which text may start wrapping according to the
* `whiteSpace` and `overflowWrap` properties.
*/
this.maxWidth = Infinity
/**
* @member {string} overflowWrap
* Defines how text wraps if the `whiteSpace` property is `normal`. Can be either `'normal'`
* to break at whitespace characters, or `'break-word'` to allow breaking within words.
* Defaults to `'normal'`.
*/
this.overflowWrap = 'normal'
/**
* @member {string} textAlign
* The horizontal alignment of each line of text within the overall text bounding box.
*/
this.textAlign = 'left'
/**
* @member {number} textIndent
* Indentation for the first character of a line; see CSS `text-indent`.
*/
this.textIndent = 0
/**
* @member {string} whiteSpace
* Defines whether text should wrap when a line reaches the `maxWidth`. Can
* be either `'normal'` (the default), to allow wrapping according to the `overflowWrap` property,
* or `'nowrap'` to prevent wrapping. Note that `'normal'` here honors newline characters to
* manually break lines, making it behave more like `'pre-wrap'` does in CSS.
*/
this.whiteSpace = 'normal'
// === Presentation properties: === //
/**
* @member {THREE.Material} material
* Defines a _base_ material to be used when rendering the text. This material will be
* automatically replaced with a material derived from it, that adds shader code to
* decrease the alpha for each fragment (pixel) outside the text glyphs, with antialiasing.
* By default it will derive from a simple white MeshBasicMaterial, but you can use any
* of the other mesh materials to gain other features like lighting, texture maps, etc.
*
* Also see the `color` shortcut property.
*/
this.material = null
/**
* @member {string|number|THREE.Color} color
* This is a shortcut for setting the `color` of the text's material. You can use this
* if you don't want to specify a whole custom `material`. Also, if you do use a custom
* `material`, this color will only be used for this particuar Text instance, even if
* that same material instance is shared across multiple Text objects.
*/
this.color = null
/**
* @member {object|null} colorRanges
* WARNING: This API is experimental and may change.
* This allows more fine-grained control of colors for individual or ranges of characters,
* taking precedence over the material's `color`. Its format is an Object whose keys each
* define a starting character index for a range, and whose values are the color for each
* range. The color value can be a numeric hex color value, a `THREE.Color` object, or
* any of the strings accepted by `THREE.Color`.
*/
this.colorRanges = null
/**
* @member {number|string} outlineWidth
* WARNING: This API is experimental and may change.
* The width of an outline/halo to be drawn around each text glyph using the `outlineColor` and `outlineOpacity`.
* Can be specified as either an absolute number in local units, or as a percentage string e.g.
* `"12%"` which is treated as a percentage of the `fontSize`. Defaults to `0`, which means
* no outline will be drawn unless an `outlineOffsetX/Y` or `outlineBlur` is set.
*/
this.outlineWidth = 0
/**
* @member {string|number|THREE.Color} outlineColor
* WARNING: This API is experimental and may change.
* The color of the text outline, if `outlineWidth`/`outlineBlur`/`outlineOffsetX/Y` are set.
* Defaults to black.
*/
this.outlineColor = 0x000000
/**
* @member {number} outlineOpacity
* WARNING: This API is experimental and may change.
* The opacity of the outline, if `outlineWidth`/`outlineBlur`/`outlineOffsetX/Y` are set.
* Defaults to `1`.
*/
this.outlineOpacity = 1
/**
* @member {number|string} outlineBlur
* WARNING: This API is experimental and may change.
* A blur radius applied to the outer edge of the text's outline. If the `outlineWidth` is
* zero, the blur will be applied at the glyph edge, like CSS's `text-shadow` blur radius.
* Can be specified as either an absolute number in local units, or as a percentage string e.g.
* `"12%"` which is treated as a percentage of the `fontSize`. Defaults to `0`.
*/
this.outlineBlur = 0
/**
* @member {number|string} outlineOffsetX
* WARNING: This API is experimental and may change.
* A horizontal offset for the text outline.
* Can be specified as either an absolute number in local units, or as a percentage string e.g. `"12%"`
* which is treated as a percentage of the `fontSize`. Defaults to `0`.
*/
this.outlineOffsetX = 0
/**
* @member {number|string} outlineOffsetY
* WARNING: This API is experimental and may change.
* A vertical offset for the text outline.
* Can be specified as either an absolute number in local units, or as a percentage string e.g. `"12%"`
* which is treated as a percentage of the `fontSize`. Defaults to `0`.
*/
this.outlineOffsetY = 0
/**
* @member {number|string} strokeWidth
* WARNING: This API is experimental and may change.
* The width of an inner stroke drawn inside each text glyph using the `strokeColor` and `strokeOpacity`.
* Can be specified as either an absolute number in local units, or as a percentage string e.g. `"12%"`
* which is treated as a percentage of the `fontSize`. Defaults to `0`.
*/
this.strokeWidth = 0
/**
* @member {string|number|THREE.Color} strokeColor
* WARNING: This API is experimental and may change.
* The color of the text stroke, if `strokeWidth` is greater than zero. Defaults to gray.
*/
this.strokeColor = defaultStrokeColor
/**
* @member {number} strokeOpacity
* WARNING: This API is experimental and may change.
* The opacity of the stroke, if `strokeWidth` is greater than zero. Defaults to `1`.
*/
this.strokeOpacity = 1
/**
* @member {number} fillOpacity
* WARNING: This API is experimental and may change.
* The opacity of the glyph's fill from 0 to 1. This behaves like the material's `opacity` but allows
* giving the fill a different opacity than the `strokeOpacity`. A fillOpacity of `0` makes the
* interior of the glyph invisible, leaving just the `strokeWidth`. Defaults to `1`.
*/
this.fillOpacity = 1
/**
* @member {number} depthOffset
* This is a shortcut for setting the material's `polygonOffset` and related properties,
* which can be useful in preventing z-fighting when this text is laid on top of another
* plane in the scene. Positive numbers are further from the camera, negatives closer.
*/
this.depthOffset = 0
/**
* @member {Array<number>} clipRect
* If specified, defines a `[minX, minY, maxX, maxY]` of a rectangle outside of which all
* pixels will be discarded. This can be used for example to clip overflowing text when
* `whiteSpace='nowrap'`.
*/
this.clipRect = null
/**
* @member {string} orientation
* Defines the axis plane on which the text should be laid out when the mesh has no extra
* rotation transform. It is specified as a string with two axes: the horizontal axis with
* positive pointing right, and the vertical axis with positive pointing up. By default this
* is '+x+y', meaning the text sits on the xy plane with the text's top toward positive y
* and facing positive z. A value of '+x-z' would place it on the xz plane with the text's
* top toward negative z and facing positive y.
*/
this.orientation = defaultOrient
/**
* @member {number} glyphGeometryDetail
* Controls number of vertical/horizontal segments that make up each glyph's rectangular
* plane. Defaults to 1. This can be increased to provide more geometrical detail for custom
* vertex shader effects, for example.
*/
this.glyphGeometryDetail = 1
/**
* @member {number|null} sdfGlyphSize
* The size of each glyph's SDF (signed distance field) used for rendering. This must be a
* power-of-two number. Defaults to 64 which is generally a good balance of size and quality
* for most fonts. Larger sizes can improve the quality of glyph rendering by increasing
* the sharpness of corners and preventing loss of very thin lines, at the expense of
* increased memory footprint and longer SDF generation time.
*/
this.sdfGlyphSize = null
/**
* @member {boolean} gpuAccelerateSDF
* When `true`, the SDF generation process will be GPU-accelerated with WebGL when possible,
* making it much faster especially for complex glyphs, and falling back to a JavaScript version
* executed in web workers when support isn't available. It should automatically detect support,
* but it's still somewhat experimental, so you can set it to `false` to force it to use the JS
* version if you encounter issues with it.
*/
this.gpuAccelerateSDF = true
this.debugSDF = false
}
/**
* Updates the text rendering according to the current text-related configuration properties.
* This is an async process, so you can pass in a callback function to be executed when it
* finishes.
* @param {function} [callback]
*/
sync(callback) {
if (this._needsSync) {
this._needsSync = false
// If there's another sync still in progress, queue
if (this._isSyncing) {
(this._queuedSyncs || (this._queuedSyncs = [])).push(callback)
} else {
this._isSyncing = true
this.dispatchEvent(syncStartEvent)
getTextRenderInfo({
text: this.text,
font: this.font,
lang: this.lang,
fontSize: this.fontSize || 0.1,
fontWeight: this.fontWeight || 'normal',
fontStyle: this.fontStyle || 'normal',
letterSpacing: this.letterSpacing || 0,
lineHeight: this.lineHeight || 'normal',
maxWidth: this.maxWidth,
direction: this.direction || 'auto',
textAlign: this.textAlign,
textIndent: this.textIndent,
whiteSpace: this.whiteSpace,
overflowWrap: this.overflowWrap,
anchorX: this.anchorX,
anchorY: this.anchorY,
colorRanges: this.colorRanges,
includeCaretPositions: true, //TODO parameterize
sdfGlyphSize: this.sdfGlyphSize,
gpuAccelerateSDF: this.gpuAccelerateSDF,
unicodeFontsURL: this.unicodeFontsURL,
}, textRenderInfo => {
this._isSyncing = false
// Save result for later use in onBeforeRender
this._textRenderInfo = textRenderInfo
// Update the geometry attributes
this.geometry.updateGlyphs(
textRenderInfo.glyphBounds,
textRenderInfo.glyphAtlasIndices,
textRenderInfo.blockBounds,
textRenderInfo.chunkedBounds,
textRenderInfo.glyphColors
)
// If we had extra sync requests queued up, kick it off
const queued = this._queuedSyncs
if (queued) {
this._queuedSyncs = null
this._needsSync = true
this.sync(() => {
queued.forEach(fn => fn && fn())
})
}
this.dispatchEvent(syncCompleteEvent)
if (callback) {
callback()
}
})
}
}
}
/**
* Initiate a sync if needed - note it won't complete until next frame at the
* earliest so if possible it's a good idea to call sync() manually as soon as
* all the properties have been set.
* @override
*/
onBeforeRender(renderer, scene, camera, geometry, material, group) {
this.sync()
// This may not always be a text material, e.g. if there's a scene.overrideMaterial present
if (material.isTroikaTextMaterial) {
this._prepareForRender(material)
}
}
/**
* Shortcut to dispose the geometry specific to this instance.
* Note: we don't also dispose the derived material here because if anything else is
* sharing the same base material it will result in a pause next frame as the program
* is recompiled. Instead users can dispose the base material manually, like normal,
* and we'll also dispose the derived material at that time.
*/
dispose() {
this.geometry.dispose()
}
/**
* @property {TroikaTextRenderInfo|null} textRenderInfo
* @readonly
* The current processed rendering data for this TextMesh, returned by the TextBuilder after
* a `sync()` call. This will be `null` initially, and may be stale for a short period until
* the asynchrous `sync()` process completes.
*/
get textRenderInfo() {
return this._textRenderInfo || null
}
/**
* Create the text derived material from the base material. Can be overridden to use a custom
* derived material.
*/
createDerivedMaterial(baseMaterial) {
return createTextDerivedMaterial(baseMaterial)
}
// Handler for automatically wrapping the base material with our upgrades. We do the wrapping
// lazily on _read_ rather than write to avoid unnecessary wrapping on transient values.
get material() {
let derivedMaterial = this._derivedMaterial
const baseMaterial = this._baseMaterial || this._defaultMaterial || (this._defaultMaterial = defaultMaterial.clone())
if (!derivedMaterial || !derivedMaterial.isDerivedFrom(baseMaterial)) {
derivedMaterial = this._derivedMaterial = this.createDerivedMaterial(baseMaterial)
// dispose the derived material when its base material is disposed:
baseMaterial.addEventListener('dispose', function onDispose() {
baseMaterial.removeEventListener('dispose', onDispose)
derivedMaterial.dispose()
})
}
// If text outline is configured, render it as a preliminary draw using Three's multi-material
// feature (see GlyphsGeometry which sets up `groups` for this purpose) Doing it with multi
// materials ensures the layers are always rendered consecutively in a consistent order.
// Each layer will trigger onBeforeRender with the appropriate material.
if (this.hasOutline()) {
let outlineMaterial = derivedMaterial._outlineMtl
if (!outlineMaterial) {
outlineMaterial = derivedMaterial._outlineMtl = Object.create(derivedMaterial, {
id: {value: derivedMaterial.id + 0.1}
})
outlineMaterial.isTextOutlineMaterial = true
outlineMaterial.depthWrite = false
outlineMaterial.map = null //???
derivedMaterial.addEventListener('dispose', function onDispose() {
derivedMaterial.removeEventListener('dispose', onDispose)
outlineMaterial.dispose()
})
}
return [
outlineMaterial,
derivedMaterial
]
} else {
return derivedMaterial
}
}
set material(baseMaterial) {
if (baseMaterial && baseMaterial.isTroikaTextMaterial) { //prevent double-derivation
this._derivedMaterial = baseMaterial
this._baseMaterial = baseMaterial.baseMaterial
} else {
this._baseMaterial = baseMaterial
}
}
hasOutline() {
return !!(this.outlineWidth || this.outlineBlur || this.outlineOffsetX || this.outlineOffsetY)
}
get glyphGeometryDetail() {
return this.geometry.detail
}
set glyphGeometryDetail(detail) {
this.geometry.detail = detail
}
get curveRadius() {
return this.geometry.curveRadius
}
set curveRadius(r) {
this.geometry.curveRadius = r
}
// Create and update material for shadows upon request:
get customDepthMaterial() {
return first(this.material).getDepthMaterial()
}
set customDepthMaterial(m) {
// future: let the user override with their own?
}
get customDistanceMaterial() {
return first(this.material).getDistanceMaterial()
}
set customDistanceMaterial(m) {
// future: let the user override with their own?
}
_prepareForRender(material) {
const isOutline = material.isTextOutlineMaterial
const uniforms = material.uniforms
const textInfo = this.textRenderInfo
if (textInfo) {
const {sdfTexture, blockBounds} = textInfo
uniforms.uTroikaSDFTexture.value = sdfTexture
uniforms.uTroikaSDFTextureSize.value.set(sdfTexture.image.width, sdfTexture.image.height)
uniforms.uTroikaSDFGlyphSize.value = textInfo.sdfGlyphSize
uniforms.uTroikaSDFExponent.value = textInfo.sdfExponent
uniforms.uTroikaTotalBounds.value.fromArray(blockBounds)
uniforms.uTroikaUseGlyphColors.value = !isOutline && !!textInfo.glyphColors
let distanceOffset = 0
let blurRadius = 0
let strokeWidth = 0
let fillOpacity
let strokeOpacity
let strokeColor
let offsetX = 0
let offsetY = 0
if (isOutline) {
let {outlineWidth, outlineOffsetX, outlineOffsetY, outlineBlur, outlineOpacity} = this
distanceOffset = this._parsePercent(outlineWidth) || 0
blurRadius = Math.max(0, this._parsePercent(outlineBlur) || 0)
fillOpacity = outlineOpacity
offsetX = this._parsePercent(outlineOffsetX) || 0
offsetY = this._parsePercent(outlineOffsetY) || 0
} else {
strokeWidth = Math.max(0, this._parsePercent(this.strokeWidth) || 0)
if (strokeWidth) {
strokeColor = this.strokeColor
uniforms.uTroikaStrokeColor.value.set(strokeColor == null ? defaultStrokeColor : strokeColor)
strokeOpacity = this.strokeOpacity
if (strokeOpacity == null) strokeOpacity = 1
}
fillOpacity = this.fillOpacity
}
uniforms.uTroikaEdgeOffset.value = distanceOffset
uniforms.uTroikaPositionOffset.value.set(offsetX, offsetY)
uniforms.uTroikaBlurRadius.value = blurRadius
uniforms.uTroikaStrokeWidth.value = strokeWidth
uniforms.uTroikaStrokeOpacity.value = strokeOpacity
uniforms.uTroikaFillOpacity.value = fillOpacity == null ? 1 : fillOpacity
uniforms.uTroikaCurveRadius.value = this.curveRadius || 0
let clipRect = this.clipRect
if (clipRect && Array.isArray(clipRect) && clipRect.length === 4) {
uniforms.uTroikaClipRect.value.fromArray(clipRect)
} else {
// no clipping - choose a finite rect that shouldn't ever be reached by overflowing glyphs or outlines
const pad = (this.fontSize || 0.1) * 100
uniforms.uTroikaClipRect.value.set(
blockBounds[0] - pad,
blockBounds[1] - pad,
blockBounds[2] + pad,
blockBounds[3] + pad
)
}
this.geometry.applyClipRect(uniforms.uTroikaClipRect.value)
}
uniforms.uTroikaSDFDebug.value = !!this.debugSDF
material.polygonOffset = !!this.depthOffset
material.polygonOffsetFactor = material.polygonOffsetUnits = this.depthOffset || 0
// Shortcut for setting material color via `color` prop on the mesh; this is
// applied only to the derived material to avoid mutating a shared base material.
const color = isOutline ? (this.outlineColor || 0) : this.color
if (color == null) {
delete material.color //inherit from base
} else {
const colorObj = material.hasOwnProperty('color') ? material.color : (material.color = new Color())
if (color !== colorObj._input || typeof color === 'object') {
colorObj.set(colorObj._input = color)
}
}
// base orientation
let orient = this.orientation || defaultOrient
if (orient !== material._orientation) {
let rotMat = uniforms.uTroikaOrient.value
orient = orient.replace(/[^-+xyz]/g, '')
let match = orient !== defaultOrient && orient.match(/^([-+])([xyz])([-+])([xyz])$/)
if (match) {
let [, hSign, hAxis, vSign, vAxis] = match
tempVec3a.set(0, 0, 0)[hAxis] = hSign === '-' ? 1 : -1
tempVec3b.set(0, 0, 0)[vAxis] = vSign === '-' ? -1 : 1
tempMat4.lookAt(origin, tempVec3a.cross(tempVec3b), tempVec3b)
rotMat.setFromMatrix4(tempMat4)
} else {
rotMat.identity()
}
material._orientation = orient
}
}
_parsePercent(value) {
if (typeof value === 'string') {
let match = value.match(/^(-?[\d.]+)%$/)
let pct = match ? parseFloat(match[1]) : NaN
value = (isNaN(pct) ? 0 : pct / 100) * this.fontSize
}
return value
}
/**
* Translate a point in local space to an x/y in the text plane.
*/
localPositionToTextCoords(position, target = new Vector2()) {
target.copy(position) //simple non-curved case is 1:1
const r = this.curveRadius
if (r) { //flatten the curve
target.x = Math.atan2(position.x, Math.abs(r) - Math.abs(position.z)) * Math.abs(r)
}
return target
}
/**
* Translate a point in world space to an x/y in the text plane.
*/
worldPositionToTextCoords(position, target = new Vector2()) {
tempVec3a.copy(position)
return this.localPositionToTextCoords(this.worldToLocal(tempVec3a), target)
}
/**
* @override Custom raycasting to test against the whole text block's max rectangular bounds
* TODO is there any reason to make this more granular, like within individual line or glyph rects?
*/
raycast(raycaster, intersects) {
const {textRenderInfo, curveRadius} = this
if (textRenderInfo) {
const bounds = textRenderInfo.blockBounds
const raycastMesh = curveRadius ? getCurvedRaycastMesh() : getFlatRaycastMesh()
const geom = raycastMesh.geometry
const {position, uv} = geom.attributes
for (let i = 0; i < uv.count; i++) {
let x = bounds[0] + (uv.getX(i) * (bounds[2] - bounds[0]))
const y = bounds[1] + (uv.getY(i) * (bounds[3] - bounds[1]))
let z = 0
if (curveRadius) {
z = curveRadius - Math.cos(x / curveRadius) * curveRadius
x = Math.sin(x / curveRadius) * curveRadius
}
position.setXYZ(i, x, y, z)
}
geom.boundingSphere = this.geometry.boundingSphere
geom.boundingBox = this.geometry.boundingBox
raycastMesh.matrixWorld = this.matrixWorld
raycastMesh.material.side = this.material.side
tempArray.length = 0
raycastMesh.raycast(raycaster, tempArray)
for (let i = 0; i < tempArray.length; i++) {
tempArray[i].object = this
intersects.push(tempArray[i])
}
}
}
copy(source) {
// Prevent copying the geometry reference so we don't end up sharing attributes between instances
const geom = this.geometry
super.copy(source)
this.geometry = geom
COPYABLE_PROPS.forEach(prop => {
this[prop] = source[prop]
})
return this
}
clone() {
return new this.constructor().copy(this)
}
}
// Create setters for properties that affect text layout:
SYNCABLE_PROPS.forEach(prop => {
const privateKey = '_private_' + prop
Object.defineProperty(Text.prototype, prop, {
get() {
return this[privateKey]
},
set(value) {
if (value !== this[privateKey]) {
this[privateKey] = value
this._needsSync = true
}
}
})
})
export {
Text
}

Xet Storage Details

Size:
28.9 kB
·
Xet hash:
0e35995cdffa44a9f0c521a6a09d6bf318415769a00f27b70cef8972060ab425

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.