!!!Obj-C 2.0 -- Chapter 4 Declared Properties

本文深入探讨了Objective-C中属性的声明与实现,包括属性声明的组成部分、属性装饰符的作用、可读写属性的使用、赋值行为、属性与实例变量的关系、析构方法的实现、子类化与属性重定义、性能与线程考虑,以及属性在不同场景下的应用,为开发者提供全面的属性使用指南。

4.2 Property Declaration and Implementation

There are two parts to a declared property, its declaration and its implementation

4.2.1 Property Declaration

A property declaration begins with the keyword @property, @property can appear in the method declaration list found in the @interface of a class, @property can also appear in the declaration of a protocol or category.

@property (attributes) type name;

// property only adds accessor to a instance variable,
// we still need to declare the instance variable 
@interface MyClass : NSObject
{
    float value;
}
@property float value;
@end

A property declaration equals to declaring two accessor methods.

@property float value;

// equals to 

- (float) value;
- (vlid) setValue:(float) newValue;

4.2.2 Property Declaration Attributes

You can decorate a property with attributes by using the form @property(attribute1, attribute2, ...). For property declarations that use a comma delimited list of variable names, theproperty attributes apply to all of the named properties.

If you use @synthesize directive to tell the compiler to create accessor methods, the code will match the specification given by the keywords. If you implement the accessor methods, you should ensure it matches the specification.

1.Accessor Method Names

The default names for the getter and setter methods associated with a property arepropertyNameandsetPropertyName: respectively. The following attributes allow you to specify custom names instead:

getter = getterName

Specifies the name of the get accessor for the property. The getter must return a type matching the property's type and take no arguments.

setter = setterName

Specifies the name of the set accessor for the property. The setter method must take a single argument of a type matching the property's type and must return void.

If you specify that a property is readonly then also specify a setter withsetter=, you will get a compiler warning.

2. Writability

These attributes specify whether or not a property has an associated set accessor. They are mutually exclusive.

readwrite

indicates that the property should be treated as read/write. This is the default. Both a getter and setter will be required in the @implementation. If you use @synthesize in the implementation block, the getter and setter methods are synthesized.

readonly

indicates that the property is read-only.

3. Setter Semantics

assign

specifies that the setter uses simple assignment. This is the default

retain

specifies that retain should be invoked on the object upon assignment.

copy

specifies that a copy of the object should be used for assignment

@property and its attributes: http://stackoverflow.com/questions/2255861/property-and-retain-assign-copy-nonatomic :

1. assign: count will not increase

2. retain: count will increase

3. copy: create another object

4. Atomicity ???

There is no keyword to denote atomic.

nonatomic

specifies that accessors are non-atomic. By default, accessors are atomic.

4.2.3 Property Implementation Directives

You can use @synthesize and @dynamic directives in @implementation blocks to trigger specific compiler actions.

The default value is @dynamic, If therefore, you do not specify either @synthesize or @dynamic for a particular property, you must provide a getter and setter method implementation for that property.

@synthesize

you use the @synthesize keyword to tell the compiler that it should synthesize the setter and/or getter methods for the propertyif you do not supply them within the @implementation block. (E.G. You can implement only setter and let the compiler implement the getter)

// Using @synthesize
@interface MyClass : NSObject
{
    NSString @value;
}
@property(copy, readwrite) NSString *value;
@end

@implementation MyClass
@synthesize value;
@end
@dynamic

You use the @dynamic keyword to tell the compiler that you will fulfill the API contract implied by a property either by providing method implementations directly or at runtime.

4.3 Using Properties

4.3.2 Property Re-declaration

You can re-declare a property in a subclass, but(with exception of readonly vs. readwrite) you must repeat its attributes in whole in the subclass.

The ability to redeclare a read-only property as read/write enables two common implementation patters:

1. A mutable subclass of an immutable class (NSString, NSArray, and NSDictionary are all examples)   ???

2. A property that has public API that is readonly but a private readwrite implementation internal to the class.

//public header file
@interface MyObject : NSObject
{
    NSString *language;
}
@property (readonly, copy) NSString *language;
@end

//private implementation file
@interface MyObject {}                 // This is a class extension
@property (readwrite, copy) NSString *language;
@end

@implementation MyObject
@synthesize language;
@end

4.3.3 Copy

If you use the copy declaration attribute, you specify that a value is copied during assignment. If you synthesize the corresponding accessor, the synthesized method uses the copy method. In this way, your object will have its own privateimmutable copy.

// If you declare a property as follows:
@property (nonatomic, copy) NSString *string;

// The synthesized method is similar to the following:
- (void)setString: (NSString *)newString {
    if (string != newString) {
        [string release];
        string = [newString copy];
    }
}
The copy method will return an immutable version, so we should use mutableCopy when necessary:

@interface MyClass : NSObject {
    NSMutableArray *myArray;
}
@property (nonatomic, copy) NSMutableArray *myArray;
@end

@implementation MyClass
@synthesize myArray;
-(void) setMyArray: (NSMutableArray *) newArray {
    if(myArray != newArray){
        [myArray release];
        myArray = [newArray mutableCopy];
    }
}
@end

4.3.4 dealloc

Properties are not aut9omatically released for you.

But you can implement your dealloc method in this way: you can look for all the property declarations in your header file and make sure thatobject properties not marked assign are released and those marked assign are not released.

Note:

Typically in a dealloc method you should release object instance variables directly(rather than invoking a set accessor and passing nil as the parameter):

- (void)dealloc {
    [property release];
    [super dealloc];
}
If you are using the modern runtime and synthesizing the instance variable, However, you cannot access the instance variable directly, so you must invoke the accessor method:

- (void) dealloc {
    [self setProperty:nil];
    [super dealloc];
}

// [self setProperty:nil] equals to
[property release];
property = nil;
// 相当于C free之后将指针设为空

4.4 Subclassing with Properties

You can override a readonly property to make it writable:

@interface MyInterger : NSObject
{
    NSInteger value;
}
@property(readonly) NSInteger value;
@end

@implementation MyInterger
@synthesize value;
@end
You can implement a subclass which redefines the property to make it writable:

@interface MyMutableInterger: MyInterger
@property(readwrite) NSInteger value;
@end

@implementation MyMutableInteger
@dynamic value;

// use the getter from super class
-(void)setValue: (NSInteger) newX {
    value = newX;
}
@end

4.5 Performance and Threading

The declaration attributes that affect performance and threading are retain, assign, copy, and nonatomic.

retain/copy/assign affect only the implementation of the assignment part of the set method:

// assign
property = newValue;

// retain
if(property != newValue) {
    [property release];
    property = [newValue retain];
}

//copy
if(property != newValue ) {
    [property release];
    property = [newValue copy];
}
Atomic left...

Property vs Instance Variable

It makes it easier to get access to the instance variable.

http://stackoverflow.com/questions/719788/property-vs-instance-variable

Run time difference P 68.









<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>爱心</title> <link rel="stylesheet" href="./css/style.css"> </head> <body> <script src='./js/three.min.js'></script> <script src='./js/TrackballControls.js'></script> <script src='./js/simplex-noise.js'></script> <script src='./js/OBJLoader.js'></script> <script src='./js/gsap.min.js'></script> <script src="./js/script.js"></script> <div id="main"></div> <script type="text/javascript"> //获取父容器 var mainObj = document.getElementById('main') //获取浏览器的高度 var innerWidth = document.body.clientWidth var innerHeight = document.body.clientHeight //计数器 var number = 0 /** * 位置随机生成 */ var interval = setInterval(function() { var heart = document.createElement('heart') heart.style.left = Math.floor(Math.random() * innerWidth) + 'px' heart.style.top = Math.floor(Math.random() * innerHeight) + 'px' mainObj.appendChild(heart) number++ //数量达到520时结束 if (number >= 520) { clearInterval(interval) } }, 50) </script> <script> (function () { const _face = new THREE.Triangle(); const _color = new THREE.Vector3(); class MeshSurfaceSampler { constructor(mesh) { let geometry = mesh.geometry; if (!geometry.isBufferGeometry || geometry.attributes.position.itemSize !== 3) { throw new Error('THREE.MeshSurfaceSampler: Requires BufferGeometry triangle mesh.'); } if (geometry.index) { console.warn('THREE.MeshSurfaceSampler: Converting geometry to non-indexed BufferGeometry.'); geometry = geometry.toNonIndexed(); } this.geometry = geometry; this.randomFunction = Math.random; this.positionAttribute = this.geometry.getAttribute('position'); this.colorAttribute = this.geometry.getAttribute('color'); this.weightAttribute = null; this.distribution = null; } setWeightAttribute(name) { this.weightAttribute = name ? this.geometry.getAttribute(name) : null; return this; } build() { const positionAttribute = this.positionAttribute; const weightAttribute = this.weightAttribute; const faceWeights = new Float32Array(positionAttribute.count / 3); // Accumulate weights for each mesh face. for (let i = 0; i < positionAttribute.count; i += 3) { let faceWeight = 1; if (weightAttribute) { faceWeight = weightAttribute.getX(i) + weightAttribute.getX(i + 1) + weightAttribute.getX(i + 2); } _face.a.fromBufferAttribute(positionAttribute, i); _face.b.fromBufferAttribute(positionAttribute, i + 1); _face.c.fromBufferAttribute(positionAttribute, i + 2); faceWeight *= _face.getArea(); faceWeights[i / 3] = faceWeight; } // Store cumulative total face weights in an array, where weight index // corresponds to face index. this.distribution = new Float32Array(positionAttribute.count / 3); let cumulativeTotal = 0; for (let i = 0; i < faceWeights.length; i++) { cumulativeTotal += faceWeights[i]; this.distribution[i] = cumulativeTotal; } return this; } setRandomGenerator(randomFunction) { this.randomFunction = randomFunction; return this; } sample(targetPosition, targetNormal, targetColor) { const cumulativeTotal = this.distribution[this.distribution.length - 1]; const faceIndex = this.binarySearch(this.randomFunction() * cumulativeTotal); return this.sampleFace(faceIndex, targetPosition, targetNormal, targetColor); } binarySearch(x) { const dist = this.distribution; let start = 0; let end = dist.length - 1; let index = - 1; while (start <= end) { const mid = Math.ceil((start + end) / 2); if (mid === 0 || dist[mid - 1] <= x && dist[mid] > x) { index = mid; break; } else if (x < dist[mid]) { end = mid - 1; } else { start = mid + 1; } } return index; } sampleFace(faceIndex, targetPosition, targetNormal, targetColor) { let u = this.randomFunction(); let v = this.randomFunction(); if (u + v > 1) { u = 1 - u; v = 1 - v; } _face.a.fromBufferAttribute(this.positionAttribute, faceIndex * 3); _face.b.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 1); _face.c.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 2); targetPosition.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v)); if (targetNormal !== undefined) { _face.getNormal(targetNormal); } if (targetColor !== undefined && this.colorAttribute !== undefined) { _face.a.fromBufferAttribute(this.colorAttribute, faceIndex * 3); _face.b.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 1); _face.c.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 2); _color.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v)); targetColor.r = _color.x; targetColor.g = _color.y; targetColor.b = _color.z; } return this; } } THREE.MeshSurfaceSampler = MeshSurfaceSampler; })(); </script> <script> (function () { const _object_pattern = /^[og]\s*(.+)?/; // mtllib file_reference const _material_library_pattern = /^mtllib /; // usemtl material_name const _material_use_pattern = /^usemtl /; // usemap map_name const _map_use_pattern = /^usemap /; const _vA = new THREE.Vector3(); const _vB = new THREE.Vector3(); const _vC = new THREE.Vector3(); const _ab = new THREE.Vector3(); const _cb = new THREE.Vector3(); function ParserState() { const state = { objects: [], object: {}, vertices: [], normals: [], colors: [], uvs: [], materials: {}, materialLibraries: [], startObject: function (name, fromDeclaration) { // If the current object (initial from reset) is not from a g/o declaration in the parsed // file. We need to use it for the first parsed g/o to keep things in sync. if (this.object && this.object.fromDeclaration === false) { this.object.name = name; this.object.fromDeclaration = fromDeclaration !== false; return; } const previousMaterial = this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined; if (this.object && typeof this.object._finalize === 'function') { this.object._finalize(true); } this.object = { name: name || '', fromDeclaration: fromDeclaration !== false, geometry: { vertices: [], normals: [], colors: [], uvs: [], hasUVIndices: false }, materials: [], smooth: true, startMaterial: function (name, libraries) { const previous = this._finalize(false); // New usemtl declaration overwrites an inherited material, except if faces were declared // after the material, then it must be preserved for proper MultiMaterial continuation. if (previous && (previous.inherited || previous.groupCount <= 0)) { this.materials.splice(previous.index, 1); } const material = { index: this.materials.length, name: name || '', mtllib: Array.isArray(libraries) && libraries.length > 0 ? libraries[libraries.length - 1] : '', smooth: previous !== undefined ? previous.smooth : this.smooth, groupStart: previous !== undefined ? previous.groupEnd : 0, groupEnd: - 1, groupCount: - 1, inherited: false, clone: function (index) { const cloned = { index: typeof index === 'number' ? index : this.index, name: this.name, mtllib: this.mtllib, smooth: this.smooth, groupStart: 0, groupEnd: - 1, groupCount: - 1, inherited: false }; cloned.clone = this.clone.bind(cloned); return cloned; } }; this.materials.push(material); return material; }, currentMaterial: function () { if (this.materials.length > 0) { return this.materials[this.materials.length - 1]; } return undefined; }, _finalize: function (end) { const lastMultiMaterial = this.currentMaterial(); if (lastMultiMaterial && lastMultiMaterial.groupEnd === - 1) { lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; lastMultiMaterial.inherited = false; } // Ignore objects tail materials if no face declarations followed them before a new o/g started. if (end && this.materials.length > 1) { for (let mi = this.materials.length - 1; mi >= 0; mi--) { if (this.materials[mi].groupCount <= 0) { this.materials.splice(mi, 1); } } } // Guarantee at least one empty material, this makes the creation later more straight forward. if (end && this.materials.length === 0) { this.materials.push({ name: '', smooth: this.smooth }); } return lastMultiMaterial; } }; // Inherit previous objects material. // Spec tells us that a declared material must be set to all objects until a new material is declared. // If a usemtl declaration is encountered while this new object is being parsed, it will // overwrite the inherited material. Exception being that there was already face declarations // to the inherited material, then it will be preserved for proper MultiMaterial continuation. if (previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function') { const declared = previousMaterial.clone(0); declared.inherited = true; this.object.materials.push(declared); } this.objects.push(this.object); }, finalize: function () { if (this.object && typeof this.object._finalize === 'function') { this.object._finalize(true); } }, parseVertexIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 3) * 3; }, parseNormalIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 3) * 3; }, parseUVIndex: function (value, len) { const index = parseInt(value, 10); return (index >= 0 ? index - 1 : index + len / 2) * 2; }, addVertex: function (a, b, c) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); dst.push(src[b + 0], src[b + 1], src[b + 2]); dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addVertexPoint: function (a) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); }, addVertexLine: function (a) { const src = this.vertices; const dst = this.object.geometry.vertices; dst.push(src[a + 0], src[a + 1], src[a + 2]); }, addNormal: function (a, b, c) { const src = this.normals; const dst = this.object.geometry.normals; dst.push(src[a + 0], src[a + 1], src[a + 2]); dst.push(src[b + 0], src[b + 1], src[b + 2]); dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addFaceNormal: function (a, b, c) { const src = this.vertices; const dst = this.object.geometry.normals; _vA.fromArray(src, a); _vB.fromArray(src, b); _vC.fromArray(src, c); _cb.subVectors(_vC, _vB); _ab.subVectors(_vA, _vB); _cb.cross(_ab); _cb.normalize(); dst.push(_cb.x, _cb.y, _cb.z); dst.push(_cb.x, _cb.y, _cb.z); dst.push(_cb.x, _cb.y, _cb.z); }, addColor: function (a, b, c) { const src = this.colors; const dst = this.object.geometry.colors; if (src[a] !== undefined) dst.push(src[a + 0], src[a + 1], src[a + 2]); if (src[b] !== undefined) dst.push(src[b + 0], src[b + 1], src[b + 2]); if (src[c] !== undefined) dst.push(src[c + 0], src[c + 1], src[c + 2]); }, addUV: function (a, b, c) { const src = this.uvs; const dst = this.object.geometry.uvs; dst.push(src[a + 0], src[a + 1]); dst.push(src[b + 0], src[b + 1]); dst.push(src[c + 0], src[c + 1]); }, addDefaultUV: function () { const dst = this.object.geometry.uvs; dst.push(0, 0); dst.push(0, 0); dst.push(0, 0); }, addUVLine: function (a) { const src = this.uvs; const dst = this.object.geometry.uvs; dst.push(src[a + 0], src[a + 1]); }, addFace: function (a, b, c, ua, ub, uc, na, nb, nc) { const vLen = this.vertices.length; let ia = this.parseVertexIndex(a, vLen); let ib = this.parseVertexIndex(b, vLen); let ic = this.parseVertexIndex(c, vLen); this.addVertex(ia, ib, ic); this.addColor(ia, ib, ic); // normals if (na !== undefined && na !== '') { const nLen = this.normals.length; ia = this.parseNormalIndex(na, nLen); ib = this.parseNormalIndex(nb, nLen); ic = this.parseNormalIndex(nc, nLen); this.addNormal(ia, ib, ic); } else { this.addFaceNormal(ia, ib, ic); } // uvs if (ua !== undefined && ua !== '') { const uvLen = this.uvs.length; ia = this.parseUVIndex(ua, uvLen); ib = this.parseUVIndex(ub, uvLen); ic = this.parseUVIndex(uc, uvLen); this.addUV(ia, ib, ic); this.object.geometry.hasUVIndices = true; } else { // add placeholder values (for inconsistent face definitions) this.addDefaultUV(); } }, addPointGeometry: function (vertices) { this.object.geometry.type = 'Points'; const vLen = this.vertices.length; for (let vi = 0, l = vertices.length; vi < l; vi++) { const index = this.parseVertexIndex(vertices[vi], vLen); this.addVertexPoint(index); this.addColor(index); } }, addLineGeometry: function (vertices, uvs) { this.object.geometry.type = 'Line'; const vLen = this.vertices.length; const uvLen = this.uvs.length; for (let vi = 0, l = vertices.length; vi < l; vi++) { this.addVertexLine(this.parseVertexIndex(vertices[vi], vLen)); } for (let uvi = 0, l = uvs.length; uvi < l; uvi++) { this.addUVLine(this.parseUVIndex(uvs[uvi], uvLen)); } } }; state.startObject('', false); return state; } // class OBJLoader extends THREE.Loader { constructor(manager) { super(manager); this.materials = null; } load(url, onLoad, onProgress, onError) { const scope = this; const loader = new THREE.FileLoader(this.manager); loader.setPath(this.path); loader.setRequestHeader(this.requestHeader); loader.setWithCredentials(this.withCredentials); loader.load(url, function (text) { try { onLoad(scope.parse(text)); } catch (e) { if (onError) { onError(e); } else { console.error(e); } scope.manager.itemError(url); } }, onProgress, onError); } setMaterials(materials) { this.materials = materials; return this; } parse(text) { const state = new ParserState(); if (text.indexOf('\r\n') !== - 1) { // This is faster than String.split with regex that splits on both text = text.replace(/\r\n/g, '\n'); } if (text.indexOf('\\\n') !== - 1) { // join lines separated by a line continuation character (\) text = text.replace(/\\\n/g, ''); } const lines = text.split('\n'); let line = '', lineFirstChar = ''; let lineLength = 0; let result = []; // Faster to just trim left side of the line. Use if available. const trimLeft = typeof ''.trimLeft === 'function'; for (let i = 0, l = lines.length; i < l; i++) { line = lines[i]; line = trimLeft ? line.trimLeft() : line.trim(); lineLength = line.length; if (lineLength === 0) continue; lineFirstChar = line.charAt(0); // @todo invoke passed in handler if any if (lineFirstChar === '#') continue; if (lineFirstChar === 'v') { const data = line.split(/\s+/); switch (data[0]) { case 'v': state.vertices.push(parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3])); if (data.length >= 7) { state.colors.push(parseFloat(data[4]), parseFloat(data[5]), parseFloat(data[6])); } else { // if no colors are defined, add placeholders so color and vertex indices match state.colors.push(undefined, undefined, undefined); } break; case 'vn': state.normals.push(parseFloat(data[1]), parseFloat(data[2]), parseFloat(data[3])); break; case 'vt': state.uvs.push(parseFloat(data[1]), parseFloat(data[2])); break; } } else if (lineFirstChar === 'f') { const lineData = line.substr(1).trim(); const vertexData = lineData.split(/\s+/); const faceVertices = []; // Parse the face vertex data into an easy to work with format for (let j = 0, jl = vertexData.length; j < jl; j++) { const vertex = vertexData[j]; if (vertex.length > 0) { const vertexParts = vertex.split('/'); faceVertices.push(vertexParts); } } // Draw an edge between the first vertex and all subsequent vertices to form an n-gon const v1 = faceVertices[0]; for (let j = 1, jl = faceVertices.length - 1; j < jl; j++) { const v2 = faceVertices[j]; const v3 = faceVertices[j + 1]; state.addFace(v1[0], v2[0], v3[0], v1[1], v2[1], v3[1], v1[2], v2[2], v3[2]); } } else if (lineFirstChar === 'l') { const lineParts = line.substring(1).trim().split(' '); let lineVertices = []; const lineUVs = []; if (line.indexOf('/') === - 1) { lineVertices = lineParts; } else { for (let li = 0, llen = lineParts.length; li < llen; li++) { const parts = lineParts[li].split('/'); if (parts[0] !== '') lineVertices.push(parts[0]); if (parts[1] !== '') lineUVs.push(parts[1]); } } state.addLineGeometry(lineVertices, lineUVs); } else if (lineFirstChar === 'p') { const lineData = line.substr(1).trim(); const pointData = lineData.split(' '); state.addPointGeometry(pointData); } else if ((result = _object_pattern.exec(line)) !== null) { // o object_name // or // g group_name // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 // let name = result[ 0 ].substr( 1 ).trim(); const name = (' ' + result[0].substr(1).trim()).substr(1); state.startObject(name); } else if (_material_use_pattern.test(line)) { // material state.object.startMaterial(line.substring(7).trim(), state.materialLibraries); } else if (_material_library_pattern.test(line)) { // mtl file state.materialLibraries.push(line.substring(7).trim()); } else if (_map_use_pattern.test(line)) { // the line is parsed but ignored since the loader assumes textures are defined MTL files // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) console.warn('THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.'); } else if (lineFirstChar === 's') { result = line.split(' '); // smooth shading // @todo Handle files that have varying smooth values for a set of faces inside one geometry, // but does not define a usemtl for each face set. // This should be detected and a dummy material created (later MultiMaterial and geometry groups). // This requires some care to not create extra material on each smooth value for "normal" obj files. // where explicit usemtl defines geometry groups. // Example asset: examples/models/obj/cerberus/Cerberus.obj /* * http://paulbourke.net/dataformats/obj/ * or * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf * * From chapter "Grouping" Syntax explanation "s group_number": * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form * surfaces, smoothing groups are either turned on or off; there is no difference between values greater * than 0." */ if (result.length > 1) { const value = result[1].trim().toLowerCase(); state.object.smooth = value !== '0' && value !== 'off'; } else { // ZBrush can produce "s" lines #11707 state.object.smooth = true; } const material = state.object.currentMaterial(); if (material) material.smooth = state.object.smooth; } else { // Handle null terminated files without exception if (line === '\0') continue; console.warn('THREE.OBJLoader: Unexpected line: "' + line + '"'); } } state.finalize(); const container = new THREE.Group(); container.materialLibraries = [].concat(state.materialLibraries); const hasPrimitives = !(state.objects.length === 1 && state.objects[0].geometry.vertices.length === 0); if (hasPrimitives === true) { for (let i = 0, l = state.objects.length; i < l; i++) { const object = state.objects[i]; const geometry = object.geometry; const materials = object.materials; const isLine = geometry.type === 'Line'; const isPoints = geometry.type === 'Points'; let hasVertexColors = false; // Skip o/g line declarations that did not follow with any faces if (geometry.vertices.length === 0) continue; const buffergeometry = new THREE.BufferGeometry(); buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(geometry.vertices, 3)); if (geometry.normals.length > 0) { buffergeometry.setAttribute('normal', new THREE.Float32BufferAttribute(geometry.normals, 3)); } if (geometry.colors.length > 0) { hasVertexColors = true; buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(geometry.colors, 3)); } if (geometry.hasUVIndices === true) { buffergeometry.setAttribute('uv', new THREE.Float32BufferAttribute(geometry.uvs, 2)); } // Create materials const createdMaterials = []; for (let mi = 0, miLen = materials.length; mi < miLen; mi++) { const sourceMaterial = materials[mi]; const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; let material = state.materials[materialHash]; if (this.materials !== null) { material = this.materials.create(sourceMaterial.name); // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. if (isLine && material && !(material instanceof THREE.LineBasicMaterial)) { const materialLine = new THREE.LineBasicMaterial(); THREE.Material.prototype.copy.call(materialLine, material); materialLine.color.copy(material.color); material = materialLine; } else if (isPoints && material && !(material instanceof THREE.PointsMaterial)) { const materialPoints = new THREE.PointsMaterial({ size: 10, sizeAttenuation: false }); THREE.Material.prototype.copy.call(materialPoints, material); materialPoints.color.copy(material.color); materialPoints.map = material.map; material = materialPoints; } } if (material === undefined) { if (isLine) { material = new THREE.LineBasicMaterial(); } else if (isPoints) { material = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false }); } else { material = new THREE.MeshPhongMaterial(); } material.name = sourceMaterial.name; material.flatShading = sourceMaterial.smooth ? false : true; material.vertexColors = hasVertexColors; state.materials[materialHash] = material; } createdMaterials.push(material); } // Create mesh let mesh; if (createdMaterials.length > 1) { for (let mi = 0, miLen = materials.length; mi < miLen; mi++) { const sourceMaterial = materials[mi]; buffergeometry.addGroup(sourceMaterial.groupStart, sourceMaterial.groupCount, mi); } if (isLine) { mesh = new THREE.LineSegments(buffergeometry, createdMaterials); } else if (isPoints) { mesh = new THREE.Points(buffergeometry, createdMaterials); } else { mesh = new THREE.Mesh(buffergeometry, createdMaterials); } } else { if (isLine) { mesh = new THREE.LineSegments(buffergeometry, createdMaterials[0]); } else if (isPoints) { mesh = new THREE.Points(buffergeometry, createdMaterials[0]); } else { mesh = new THREE.Mesh(buffergeometry, createdMaterials[0]); } } mesh.name = object.name; container.add(mesh); } } else { // if there is only the default parser state object with no geometry data, interpret data as point cloud if (state.vertices.length > 0) { const material = new THREE.PointsMaterial({ size: 1, sizeAttenuation: false }); const buffergeometry = new THREE.BufferGeometry(); buffergeometry.setAttribute('position', new THREE.Float32BufferAttribute(state.vertices, 3)); if (state.colors.length > 0 && state.colors[0] !== undefined) { buffergeometry.setAttribute('color', new THREE.Float32BufferAttribute(state.colors, 3)); material.vertexColors = true; } const points = new THREE.Points(buffergeometry, material); container.add(points); } } return container; } } THREE.OBJLoader = OBJLoader; })(); </script> </body> </html>
06-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值